Skip to main content

commerce-app-admin-ui

Add or modify Adobe Commerce Admin UI extensions on the commerce/backend-ui/2 extension point: custom grid columns, mass actions, order view buttons, a custom Admin menu entry, and custom ACL resources. Use whenever the user wants to extend the Commerce Admin — add a column to the order, product, or customer grid, add a bulk/mass action to a grid, add a button to the order view page, add a custom menu item or page, or declare custom permissions the app checks itself — even when they don't name the extension point.

Jump to install

Source facts

Repository
adobe/skills
Last source activity
September 15, 2026 at 16:28
Detected SKILL.md language
English
Stars
182
Forks
73

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

File Explorer
7 files

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
commerce-app-admin-ui
description
Add or modify Adobe Commerce Admin UI extensions on the commerce/backend-ui/2 extension point: custom grid columns, mass actions, order view buttons, a custom Admin menu entry, and custom ACL resources. Use whenever the user wants to extend the Commerce Admin — add a column to the order, product, or customer grid, add a bulk/mass action to a grid, add a button to the order view page, add a custom menu item or page, or declare custom permissions the app checks itself — even when they don't name the extension point.
license
Apache-2.0
compatibility
Requires Node.js 22+, aio CLI, @adobe/aio-commerce-lib-app, and @adobe/aio-commerce-sdk (for Admin UI runtime action handlers). View variants use @adobe/aio-commerce-lib-admin-ui/web (installed automatically by the web-src scaffold). Requires a base app initialized with commerce-app-init.
metadata
{"author":"adobe"}
# Configure Commerce App Admin UI Adds or modifies the `adminUi` block in an existing `app.commerce.config.ts`. The Admin UI extension point (`commerce/backend-ui/2`) lets a Commerce app extend the Commerce Admin with custom grid columns, mass actions, order view buttons, a menu entry, and custom ACL resources. Other extensibility domains (webhooks, events, business config) are added separately via their own skills. ## Prerequisites - Verify the app is **scaffolded and initialized**, not merely that the config exists. Require **both**: - `app.commerce.config.ts` present in the project root, **and** - the project initialized — signalled by the generated `src/commerce-extensibility-1/` directory and installed `node_modules` (the `@adobe/aio-commerce-lib-app` dependency). - If `app.commerce.config.ts` is **missing**, stop and invoke `commerce-app-init` first (it writes the config, then runs init). - If the config is **present but the project is not initialized** (no `src/commerce-extensibility-1/` or `node_modules`), run `npx @adobe/aio-commerce-lib-app init` before continuing. Init is idempotent — it finds the existing config, skips the interactive prompts, installs dependencies, and generates the project files. - Actions can be authored in TypeScript only once the project has the TypeScript build setup (`webpack-config.cjs` + root `tsconfig.json`) that `init` scaffolds for a TypeScript Commerce config — see `commerce-app-init`. Otherwise, author actions in JavaScript. ## Extension points at a glance | Extension point | Entities | Variants | Server handler | Reference | | ------------------ | ----------------------------- | ------------- | -------------- | ------------------------------------------------------ | | Grid columns | order, product, customer | worker only | yes | [grid-columns](references/grid-columns.md) | | Mass actions | order, product, customer | view / worker | worker only | [mass-actions](references/mass-actions.md) | | Order view buttons | order only | view / worker | worker only | [order-view-buttons](references/order-view-buttons.md) | | Menu | single entry (`adminUi.menu`) | view (iframe) | no | [menu](references/menu.md) | | Custom ACL | list (`adminUi.acl`) | none | no | [custom-acl](references/custom-acl.md) | `view` renders an iframe into the app's web UI (`web-src`) at the entry's `path`; `worker` invokes a runtime action server-side. Custom ACL resources have no variant and no handler: they are standalone permissions Commerce renders in the User Roles tree, and the app checks them itself (see [custom-acl](references/custom-acl.md)). Grid columns are always worker; the menu is always an iframe. ## Step 1 — Understand intent For each thing the user wants to add, gather: - **Which extension point** — grid columns, mass actions, order view buttons, menu, or custom ACL resources - **Which entity** — `order`, `product`, or `customer` (grid columns and mass actions; view buttons are order-only; menu and custom ACL resources have no entity) - **For mass actions and view buttons, the variant** — `worker` (runtime action) or `view` (iframe into `web-src`) - The fields for that extension point (column definitions, button labels, menu parent, etc.) — see the reference file in the table above for the full field set ## Step 2 — Declare in `app.commerce.config.ts` Add (or merge into) the top-level `adminUi` block, preserving all other domains. If `adminUi` already exists, merge into it rather than replacing — keep existing entities, the menu, and existing array entries. These fields are shared across the extension points: | Field | Constraint | | -------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | `runtimeAction` | `<package>/<action>`; must resolve to a handler action you declare (Step 4) | | `confirm` | Optional `{ title?, message? }` — confirmation dialog before the action runs | | `notifications` | Optional `{ success?, error? }` — toast text Commerce shows on completion | | `sandboxPermissions` | Optional array (view/iframe entries); non-empty, no duplicates; each one of `allow-downloads`, `allow-modals`, `allow-popups` | Minimal example covering each feature (use only the parts you need): ```ts import { MENU_SALES } from "@adobe/aio-commerce-sdk/admin-ui/menu"; // inside defineConfig({ ... }): adminUi: { order: { // Custom column on the order grid (worker only) gridColumns: { label: "Fulfillment data", description: "Fulfillment status from the warehouse system.", runtimeAction: "my-app/order-grid", // <package>/<action> — declare in Step 4 columns: [ { id: "fulfillment_status", label: "Fulfillment", type: "string", align: "left" }, ], }, // Bulk action on selected orders (worker variant shown) massActions: [ { type: "worker", id: "archive-orders", label: "Archive", runtimeAction: "my-app/archive-orders", selectionLimit: 500 }, ], // Button on the order view page (worker variant shown) viewButtons: [ { type: "worker", id: "sync-inventory", label: "Sync inventory", runtimeAction: "my-app/sync-inventory" }, ], }, // Custom Admin menu entry (iframe into the app) menu: { id: "my_app_dashboard", // letters, digits, / : _ only label: "My Dashboard", description: "Custom dashboard for my app.", parentMenu: MENU_SALES, }, // Custom ACL resources — standalone permissions the app checks itself (no handler) acl: [ { id: "reports", label: "Reports", children: [ { id: "export", label: "Export" }, ]}, { id: "approve_refunds", label: "Approve Refunds" }, ], } ``` For the `view` variants (iframe) and the complete field set and constraints of each extension point, read the matching reference file under [References](#references) before writing. ## Step 3 — Register the extension point Run init so that `commerce/backend-ui/2` is added to `app.config.yaml` and `install.yaml`, and the `src/commerce-backend-ui-2/` extension folder is generated. This is idempotent — safe to run even if the extension is already registered. ```sh npx @adobe/aio-commerce-lib-app init ``` The build derives the extension's `ext.config.yaml` from your `adminUi` config: each worker `runtimeAction` becomes a `workerProcess` operation, and when you declare a `menu` or any `view`-type entry, a `view` operation plus an explicit `web: web-src` key are written. Those `hooks`, `operations`, and `web` sections are managed by the library — do not hand-edit them. When a `view` operation is present, init/generate also scaffolds the web frontend automatically (skipped if `web-src/index.html` already exists). It generates `src/commerce-backend-ui-2/web-src/` — `index.html`, `src/app.jsx`, `src/pages/main-page.jsx`, `src/components/welcome.jsx` (`.tsx` plus an independent `tsconfig.json` when the app config is TypeScript) — adds the `#web/*` import alias to `package.json`, and declares and installs pinned versions of `react`, `react-dom`, `@react-spectrum/s2`, and `@adobe/aio-commerce-lib-admin-ui` (React and Spectrum S2 are optional peer dependencies of the admin-ui library), plus some `devDependencies` for proper TypeScript and Babel support/config. A separate required-file phase runs on every generation to ensure a set of web source support files is present without replacing existing versions. The current set includes `web-src/.babelrc`, which selects React's automatic JSX transform for each environment so development builds retain JSX diagnostics while production builds do not emit `jsxDEV` calls. If `BABEL_ENV` is set, keep it synchronized with `NODE_ENV`, because Babel gives `BABEL_ENV` precedence when selecting the configuration environment. TypeScript scaffolds also add `typecheck:web-src` to the project’s composed `typecheck` script. Do not hand-pick different versions of these dependencies; the scaffold fails if incompatible versions are already installed. If the scaffold is skipped because `web-src` already exists, check its `package.json` for classic React Spectrum (`@adobe/react-spectrum` or `@react-spectrum/<component>` without `s2`) instead of `@react-spectrum/s2`. The two are compatible, but S2 is the version Adobe recommends moving to and the one this scaffold targets — suggest upgrading. This skill only configures the Admin UI extension, it doesn't drive the upgrade itself, so point the user to the `commerce-app-migrate` skill for the actual migration: ```sh npx skills add adobe/skills --skill commerce-app-migrate ``` Do not install it or perform the upgrade yourself unless the user asks. ## Step 4 — Implement the handlers What you implement depends on the variant. Examples below are in TypeScript; if the project uses JavaScript, omit type imports and annotations. ### Worker variants (grid columns, worker mass actions, worker view buttons) Each worker `runtimeAction` needs an action you declare and implement, inside the Admin UI extension folder `src/commerce-backend-ui-2/`. 1. Put the handler source under `src/commerce-backend-ui-2/actions/`. 2. Declare it in `src/commerce-backend-ui-2/ext.config.yaml` under your own package in `runtimeManifest` (any package name). The build manages `hooks` and `operations` in this file but preserves the packages you add under `runtimeManifest`, so your action survives rebuilds: ```yaml # src/commerce-backend-ui-2/ext.config.yaml runtimeManifest: packages: my-app: # must match the <package> in runtimeAction actions: order-grid: function: actions/order-grid/index.js # relative to src/commerce-backend-ui-2/ web: "yes" runtime: nodejs:24 annotations: require-adobe-auth: true # Commerce calls the action with an IMS token — validate it final: true ``` The `<package>/<action>` in `runtimeAction` maps directly: `my-app/order-grid` → package `my-app`, action `order-grid`. Commerce invokes these worker actions with an Adobe IMS token, so they need `require-adobe-auth: true` (and `final: true` to lock the bound inputs). Implement the handler with the wire-contract builders from the `@adobe/aio-commerce-sdk/admin-ui/*` entrypoints. The builders differ per extension point — the reference file gives the exact request shape and response builders. Grid columns example: ```typescript // src/commerce-backend-ui-2/actions/order-grid/index.ts import { parseGridRequest, okGridResponse, errorGridResponse, } from "@adobe/aio-commerce-sdk/admin-ui/grid-columns"; import type { RuntimeActionParams } from "@adobe/aio-commerce-sdk/core/params"; export async function main(params: RuntimeActionParams) { const { gridType, ids } = parseGridRequest(params); try { const rows = await fetchRows(gridType, ids); // row keys must match the column ids declared in config return okGridResponse(rows, { fulfillment_status: "unknown" }); } catch (error) { return errorGridResponse( 500, error instanceof Error ? error.message : String(error), ); } } ``` ### View variants (view mass actions, view buttons, menu) No server handler. Commerce opens an iframe into the app's `web-src` at the entry's `path` — and that frontend was generated for you in Step 3 (`index.html`, `src/app.jsx`, `src/pages/main-page.jsx`, `src/components/welcome.jsx`). The generated `src/app.jsx` mounts the `commerce/backend-ui/2` iframe app with `createExtensionApp` from `@adobe/aio-commerce-lib-admin-ui/web`, and its `routes` array must start with the index route: ```jsx // src/commerce-backend-ui-2/web-src/src/app.jsx (generated) import { createExtensionApp } from "@adobe/aio-commerce-lib-admin-ui/web"; import "@react-spectrum/s2/page.css"; import config from "#app.commerce.config"; import { MainPage } from "#web/pages/main-page.jsx"; createExtensionApp({ metadata: { extensionId: config.metadata.id }, routes: [{ index: true, element: <MainPage /> }], }); ``` Just as the worker variants wire a `runtimeAction` to an action, each `view` entry's `path` must be wired to a page and a route. Scaffold that wiring per entry — but **only create what is missing. Never overwrite or modify an existing page or route.** The **menu** has no `path`: it renders at the index route (`src/pages/main-page.jsx`), which the scaffold already created as a plain page. There is nothing to scaffold for the menu — leave `main-page.jsx` in place (customize its content if you like). It needs only the `MENU_*` constant for `parentMenu` in the config (see [menu](references/menu.md)). For each `view`-type **mass action** and **order view button** (both carry a `path`): 1. **Check for the route.** Look in `src/app.jsx` for a `routes` entry whose `path` equals the entry's config `path`, and in `web-src/src/pages/` for its page file. If a route for that `path` already exists, leave it and its page untouched and move on. 2. **Create the placeholder page (missing only).** Add `web-src/src/pages/<name>.jsx` — use `.tsx` (and TypeScript) when the app config is TypeScript. Keep it minimal, matching the look of the generated `main-page`/`welcome` (a `<main>` with a heading). Do not add a copyright header — the generated `web-src` files carry none. Pre-wire the context hook for the entry type (table below). 3. **Register the route in `src/app.jsx` (missing only).** Import the page via the `#web/pages/*` alias and append a `{ path, element }` entry to `routes`. Write `path` as the **exact same string as the config `path`, including the leading `#/`** — copy it verbatim so the route and the config entry visibly line up. Keep the index route first. Pre-wire the hook by view type — all from `@adobe/aio-commerce-lib-admin-ui/web`: | View entry | Context hook | Also | Reference |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub