Loading...
Loading...
Add or modify Adobe Commerce Admin UI extensions on the commerce/backend-ui/2 extension point: custom grid columns, mass actions, order view buttons, and a custom Admin menu entry. 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, or add a custom menu item or page — even when they don't name the extension point.
npx skill4agent add adobe/skills commerce-app-admin-uiadminUiapp.commerce.config.tscommerce/backend-ui/2app.commerce.config.tssrc/commerce-extensibility-1/node_modules@adobe/aio-commerce-lib-appapp.commerce.config.tscommerce-app-initsrc/commerce-extensibility-1/node_modulesnpx @adobe/aio-commerce-lib-app initwebpack-config.cjstsconfig.jsoninitcommerce-app-init| Extension point | Entities | Variants | Server handler | Reference |
|---|---|---|---|---|
| Grid columns | order, product, customer | worker only | yes | grid-columns |
| Mass actions | order, product, customer | view / worker | worker only | mass-actions |
| Order view buttons | order only | view / worker | worker only | order-view-buttons |
| Menu | single entry ( | view (iframe) | no | menu |
viewweb-srcpathworkerorderproductcustomerworkerviewweb-srcapp.commerce.config.tsadminUiadminUi| Field | Constraint |
|---|---|
| |
| Optional |
| Optional |
| Optional array (view/iframe entries); non-empty, no duplicates; each one of |
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,
},
}viewcommerce/backend-ui/2app.config.yamlinstall.yamlsrc/commerce-backend-ui-2/npx @adobe/aio-commerce-lib-app initext.config.yamladminUiruntimeActionworkerProcessmenuviewviewweb: web-srchooksoperationswebviewweb-src/index.htmlsrc/commerce-backend-ui-2/web-src/index.htmlsrc/app.jsxsrc/pages/main-page.jsxsrc/components/welcome.jsx.tsxtsconfig.json#web/*package.jsonreactreact-dom@react-spectrum/s2@adobe/aio-commerce-lib-admin-uidevDependenciestypecheck:web-srctypecheckweb-srcpackage.json@adobe/react-spectrum@react-spectrum/<component>s2@react-spectrum/s2commerce-app-migratenpx skills add adobe/skills --skill commerce-app-migrateruntimeActionsrc/commerce-backend-ui-2/src/commerce-backend-ui-2/actions/src/commerce-backend-ui-2/ext.config.yamlruntimeManifesthooksoperationsruntimeManifest# 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<package>/<action>runtimeActionmy-app/order-gridmy-apporder-gridrequire-adobe-auth: truefinal: true@adobe/aio-commerce-sdk/admin-ui/*// 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),
);
}
}web-srcpathindex.htmlsrc/app.jsxsrc/pages/main-page.jsxsrc/components/welcome.jsxsrc/app.jsxcommerce/backend-ui/2createExtensionApp@adobe/aio-commerce-lib-admin-ui/webroutes// 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 /> }],
});runtimeActionviewpathpathsrc/pages/main-page.jsxmain-page.jsxMENU_*parentMenuviewpathsrc/app.jsxroutespathpathweb-src/src/pages/pathweb-src/src/pages/<name>.jsx.tsxmain-pagewelcome<main>web-srcsrc/app.jsx#web/pages/*{ path, element }routespathpath#/@adobe/aio-commerce-lib-admin-ui/web| View entry | Context hook | Also | Reference |
|---|---|---|---|
Mass action ( | | | mass-actions |
| Order view button | | | order-view-buttons |
| Menu | none (plain index page) | — | menu |
useIms()useCommerce(){ data, error }errordata.imsTokendata.imsOrgIduseIms()data.commerceHostuseCommerce()WelcomeuseIms()view// src/commerce-backend-ui-2/web-src/src/pages/export-customers.jsx
import {
useHostConnection,
useMassActionContext,
} from "@adobe/aio-commerce-lib-admin-ui/web";
export function ExportCustomersPage() {
const { data, error: contextError } = useMassActionContext();
const { actions, error: hostError } = useHostConnection();
if (contextError) throw contextError;
if (hostError) throw hostError;
const { selectedIds } = data; // non-empty string[] — the selected record ids
const { close } = actions; // await close() (or actions.closeWithError()) when done
return (
<main>
<h1>Export customers</h1>
<p>{selectedIds.length} selected</p>
</main>
);
}// src/commerce-backend-ui-2/web-src/src/app.jsx
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";
import { ExportCustomersPage } from "#web/pages/export-customers.jsx";
createExtensionApp({
metadata: { extensionId: config.metadata.id },
routes: [
{ index: true, element: <MainPage /> }, // keep the index route first
{ path: "#/export-customers", element: <ExportCustomersPage /> }, // path === config `path`
],
});useOrderViewButtonContext()data.orderIderroraio app buildadminUiviewworkerworkerruntimeActionpathsandboxPermissionsviewpathruntimeActiontimeouttypevieworderproductcustomergridColumnsorderviewButtonsruntimeAction<package>/<action>runtimeManifestsrc/commerce-backend-ui-2/ext.config.yamlworkerProcessruntimeManifestsrc/commerce-backend-ui-2/src/commerce-extensibility-1/functionsrc/commerce-backend-ui-2/okGridResponseidgridColumns.columnspath{ path }src/app.jsxpathidid/:_defineConfigdefineConfig@adobe/aio-commerce-lib-app/configcreateExtensionApp<StrictMode>aio app devaio app runaio app buildruntimeActionruntimeManifestsrc/commerce-backend-ui-2/ext.config.yamlaio app buildcommerce-app-business-configcommerce-app-webhookscommerce-app-eventingcommerce-app-storage