commerce-app-admin-ui

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Configure Commerce App Admin UI

配置Commerce应用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, and a menu entry. Other extensibility domains (webhooks, events, business config) are added separately via their own skills.
添加或修改现有
app.commerce.config.ts
中的
adminUi
块。 Admin UI扩展点(
commerce/backend-ui/2
)允许Commerce应用通过自定义网格列、批量操作、订单视图按钮和菜单条目来扩展Commerce Admin。 其他扩展领域(webhooks、事件、业务配置)需通过各自的技能单独添加。

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.
  • 验证应用已完成脚手架搭建并初始化,而不仅仅是存在配置文件。需同时满足以下两点:
    • 项目根目录下存在
      app.commerce.config.ts
      并且
    • 项目已初始化——标志为生成了
      src/commerce-extensibility-1/
      目录且已安装
      node_modules
      (包含
      @adobe/aio-commerce-lib-app
      依赖)。
  • 如果缺少
    app.commerce.config.ts
    ,请先停止操作并调用
    commerce-app-init
    (该工具会写入配置文件,然后执行初始化)。
  • 如果配置文件存在但项目未初始化(无
    src/commerce-extensibility-1/
    node_modules
    ),请先运行
    npx @adobe/aio-commerce-lib-app init
    再继续。初始化操作是幂等的——它会找到现有配置,跳过交互式提示,安装依赖并生成项目文件。
  • 只有当项目具备
    init
    为TypeScript Commerce配置搭建的TypeScript构建环境(
    webpack-config.cjs
    + 根目录
    tsconfig.json
    )时,才能使用TypeScript编写动作——详情请查看
    commerce-app-init
    。否则,请使用JavaScript编写动作。

Extension points at a glance

扩展点概览

Extension pointEntitiesVariantsServer handlerReference
Grid columnsorder, product, customerworker onlyyesgrid-columns
Mass actionsorder, product, customerview / workerworker onlymass-actions
Order view buttonsorder onlyview / workerworker onlyorder-view-buttons
Menusingle entry (
adminUi.menu
)
view (iframe)nomenu
view
renders an iframe into the app's web UI (
web-src
) at the entry's
path
;
worker
invokes a runtime action server-side. Grid columns are always worker; the menu is always an iframe.
扩展点实体类型变体类型服务器处理程序参考文档
Grid columnsorder、product、customerworker onlygrid-columns
Mass actionsorder、product、customerview / workerworker onlymass-actions
Order view buttonsorder onlyview / workerworker onlyorder-view-buttons
Menu单个条目(
adminUi.menu
view (iframe)menu
view
会在条目指定的
path
位置,将iframe嵌入应用的Web UI(
web-src
)中;
worker
会在服务器端调用运行时动作。 Grid columns始终为worker变体;Menu始终为iframe形式的view变体。

Step 1 — Understand intent

步骤1 — 明确需求

For each thing the user wants to add, gather:
  • Which extension point — grid columns, mass actions, order view buttons, or menu
  • Which entity
    order
    ,
    product
    , or
    customer
    (grid columns and mass actions; view buttons are order-only; menu has 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
针对用户想要添加的每个功能,收集以下信息:
  • 扩展点类型——grid columns、mass actions、order view buttons或menu
  • 实体类型——
    order
    product
    customer
    (grid columns和mass actions支持这些实体;view buttons仅支持order;menu无实体)
  • 对于mass actions和view buttons,指定变体类型——
    worker
    (运行时动作)或
    view
    (嵌入
    web-src
    的iframe)
  • 该扩展点所需的字段(列定义、按钮标签、菜单父项等)——完整字段集请查看上方表格中的参考文档

Step 2 — Declare in
app.commerce.config.ts

步骤2 — 在
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:
FieldConstraint
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,
  },
}
For the
view
variants (iframe) and the complete field set and constraints of each extension point, read the matching reference file under References before writing.
添加(或合并到)顶层的
adminUi
块,保留所有其他领域的配置。如果
adminUi
已存在,请合并配置而非替换——保留现有实体、菜单以及现有数组条目。
以下字段为各扩展点共享:
字段约束条件
runtimeAction
<package>/<action>
;必须指向你声明的处理程序动作(步骤4)
confirm
可选的
{ title?, message? }
——动作执行前的确认对话框
notifications
可选的
{ success?, error? }
——Commerce在动作完成后显示的提示文本
sandboxPermissions
可选数组(适用于view/iframe条目);非空且无重复项;每个值为
allow-downloads
allow-modals
allow-popups
中的一个
涵盖各功能的极简示例(仅使用你需要的部分):
ts
import { MENU_SALES } from "@adobe/aio-commerce-sdk/admin-ui/menu";

// inside defineConfig({ ... }):
adminUi: {
  order: {
    // 订单网格上的自定义列(仅worker变体)
    gridColumns: {
      label: "Fulfillment data",
      description: "Fulfillment status from the warehouse system.",
      runtimeAction: "my-app/order-grid", // <package>/<action> — 在步骤4中声明
      columns: [
        { id: "fulfillment_status", label: "Fulfillment", type: "string", align: "left" },
      ],
    },
    // 选中订单的批量操作(展示worker变体)
    massActions: [
      { type: "worker", id: "archive-orders", label: "Archive",
        runtimeAction: "my-app/archive-orders", selectionLimit: 500 },
    ],
    // 订单视图页面上的按钮(展示worker变体)
    viewButtons: [
      { type: "worker", id: "sync-inventory", label: "Sync inventory",
        runtimeAction: "my-app/sync-inventory" },
    ],
  },
  // 自定义管理菜单条目(嵌入应用的iframe)
  menu: {
    id: "my_app_dashboard", // 仅允许字母、数字、/ : _
    label: "My Dashboard",
    description: "Custom dashboard for my app.",
    parentMenu: MENU_SALES,
  },
}
对于view变体(iframe)以及各扩展点的完整字段集和约束条件,请在编写前阅读参考文档中的对应文档。

Step 3 — Register the extension point

步骤3 — 注册扩展点

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 support/config. 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.
运行init命令,将
commerce/backend-ui/2
添加到
app.config.yaml
install.yaml
中,并生成
src/commerce-backend-ui-2/
扩展文件夹。此操作是幂等的——即使扩展已注册,运行该命令也是安全的。
sh
npx @adobe/aio-commerce-lib-app init
构建过程会从你的
adminUi
配置生成扩展的
ext.config.yaml
:每个worker类型的
runtimeAction
会变为
workerProcess
操作;当你声明menu或任何view类型的条目时,会写入一个
view
操作以及显式的
web: web-src
键。这些
hooks
operations
web
部分由库管理——请勿手动编辑。
当存在view操作时,init/generate会自动搭建Web前端(如果
web-src/index.html
已存在则跳过)。它会生成
src/commerce-backend-ui-2/web-src/
——包含
index.html
src/app.jsx
src/pages/main-page.jsx
src/components/welcome.jsx
(当应用配置为TypeScript时,会生成
.tsx
文件以及独立的
tsconfig.json
)——将
#web/*
导入别名添加到
package.json
中,并声明安装
react
react-dom
@react-spectrum/s2
@adobe/aio-commerce-lib-admin-ui
的固定版本(React和Spectrum S2是admin-ui库的可选对等依赖),同时添加一些
devDependencies
以支持TypeScript配置。TypeScript脚手架还会将
typecheck:web-src
添加到项目的组合
typecheck
脚本中。请勿手动选择这些依赖的其他版本;如果已安装不兼容的版本,脚手架会失败。
如果因为
web-src
已存在而跳过脚手架,请检查其
package.json
中是否使用了经典React Spectrum(
@adobe/react-spectrum
或不带
s2
@react-spectrum/<component>
)而非
@react-spectrum/s2
。两者兼容,但S2是Adobe推荐迁移的版本,也是此脚手架的目标版本——建议进行升级。本技能仅配置Admin UI扩展,不负责驱动升级本身,因此请引导用户使用
commerce-app-migrate
技能进行实际迁移:
sh
npx skills add adobe/skills --skill commerce-app-migrate
除非用户要求,否则请勿自行安装或执行升级操作。

Step 4 — Implement the handlers

步骤4 — 实现处理程序

What you implement depends on the variant. Examples below are in TypeScript; if the project uses JavaScript, omit type imports and annotations.
你需要实现的内容取决于变体类型。以下示例为TypeScript版本;如果项目使用JavaScript,请省略类型导入和注解。

Worker variants (grid columns, worker mass actions, worker view buttons)

Worker变体(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
undefined
每个worker类型的
runtimeAction
都需要你在Admin UI扩展文件夹
src/commerce-backend-ui-2/
中声明并实现一个动作。
  1. 将处理程序源码放在
    src/commerce-backend-ui-2/actions/
    下。
  2. src/commerce-backend-ui-2/ext.config.yaml
    runtimeManifest
    下,在你自己的包中声明该动作(包名任意)。构建过程会管理此文件中的
    hooks
    operations
    ,但会保留你在
    runtimeManifest
    下添加的包,因此你的动作会在重建后保留:
yaml
undefined

src/commerce-backend-ui-2/ext.config.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),
    );
  }
}
runtimeManifest: packages: my-app: # 必须与runtimeAction中的<package>匹配 actions: order-grid: function: actions/order-grid/index.js # 相对于src/commerce-backend-ui-2/ web: "yes" runtime: nodejs:24 annotations: require-adobe-auth: true # Commerce会使用IMS令牌调用该动作——请验证令牌 final: true

`runtimeAction`中的`<package>/<action>`会直接映射:`my-app/order-grid` → 包`my-app`,动作`order-grid`。Commerce会使用Adobe IMS令牌调用这些worker动作,因此它们需要`require-adobe-auth: true`(以及`final: true`以锁定绑定输入)。

使用`@adobe/aio-commerce-sdk/admin-ui/*`入口点的 wire-contract builders实现处理程序。
不同扩展点的builders不同——参考文档给出了确切的请求格式和响应builders。Grid columns示例:

```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);
    // 行键必须与config中声明的列id匹配
    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)

View变体(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).
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 entryContext hookAlsoReference
Mass action (
view
)
useMassActionContext()
{ data, error }
; use
data.selectedIds
useHostConnection()
{ actions, error }
; use
actions.close()
mass-actions
Order view button
useOrderViewButtonContext()
{ data, error }
; use
data.orderId
useHostConnection()
{ actions, error }
; use
actions.close()
order-view-buttons
Menunone (plain index page)menu
These hooks return errors instead of throwing them. A route can throw a returned error during render to send it to the SDK's error boundary, which replaces the extension content with its fallback UI. If the route must stay mounted, handle the error locally by rendering a message, offering a retry, disabling the affected feature, or providing another degraded state. The placeholder examples below throw because they don't define custom recovery UI.
A route component may also call
useIms()
and
useCommerce()
to reach the Commerce REST API or retrieve data directly from the host, respectively. Both return
{ data, error }
; after handling
error
, read
data.imsToken
and
data.imsOrgId
from
useIms()
, and
data.commerceHost
from
useCommerce()
. The generated
Welcome
component demonstrates this result handling for
useIms()
. Add those hooks only when the page actually needs them.
Example — a
view
mass action placeholder page and its route registration:
jsx
// 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>
  );
}
jsx
// 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`
  ],
});
For an order view button, swap the hook for
useOrderViewButtonContext()
and read
data.orderId
after handling
error
— see order-view-buttons.
无需服务器处理程序。Commerce会在条目指定的
path
位置,将iframe嵌入应用的
web-src
中——该前端已在步骤3中为你生成(
index.html
src/app.jsx
src/pages/main-page.jsx
src/components/welcome.jsx
)。生成的
src/app.jsx
会使用
@adobe/aio-commerce-lib-admin-ui/web
中的
createExtensionApp
挂载
commerce/backend-ui/2
iframe应用,其
routes
数组必须以索引路由开头:
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 /> }],
});
就像worker变体将
runtimeAction
连接到动作一样,每个view条目的
path
必须连接到页面和路由。为每个条目搭建此连接——但仅创建缺失的内容。切勿覆盖或修改现有页面或路由。
Menu没有
path
:它在索引路由(
src/pages/main-page.jsx
)处渲染,脚手架已将其创建为普通页面。无需为Menu搭建额外内容——保留
main-page.jsx
即可(可根据需要自定义其内容)。它仅需要config中
parentMenu
对应的
MENU_*
常量(请查看menu)。
对于每个view类型的mass actionorder view button(均包含
path
):
  1. 检查路由是否存在。在
    src/app.jsx
    中查找
    path
    与条目配置中
    path
    相等的
    routes
    条目,并在
    web-src/src/pages/
    中查找对应的页面文件。如果该
    path
    的路由已存在,请保留其页面并继续下一步。
  2. 创建占位页面(仅当缺失时)。添加
    web-src/src/pages/<name>.jsx
    ——当应用配置为TypeScript时,使用
    .tsx
    (并使用TypeScript)。保持页面简洁,与生成的
    main-page
    /
    welcome
    外观一致(包含
    <main>
    和标题)。请勿添加版权头——生成的
    web-src
    文件不包含版权头。根据条目类型预连接上下文钩子(如下表所示)。
  3. src/app.jsx
    中注册路由(仅当缺失时)
    。通过
    #web/pages/*
    别名导入页面,并将
    { path, element }
    条目追加到
    routes
    中。
    path
    需与配置中的
    path
    完全相同,包括开头的
    #/
    ——直接复制该字符串,使路由和配置条目清晰对应。保持索引路由在首位。
根据view类型预连接钩子——所有钩子均来自
@adobe/aio-commerce-lib-admin-ui/web
View条目上下文钩子补充说明参考文档
Mass action(
view
useMassActionContext()
{ data, error }
;使用
data.selectedIds
useHostConnection()
{ actions, error }
;使用
actions.close()
mass-actions
Order view button
useOrderViewButtonContext()
{ data, error }
;使用
data.orderId
useHostConnection()
{ actions, error }
;使用
actions.close()
order-view-buttons
Menu无(普通索引页面)menu
这些钩子会返回错误而非抛出错误。路由在渲染期间可以抛出返回的错误,将其发送到SDK的错误边界,错误边界会用回退UI替换扩展内容。如果路由必须保持挂载,请在本地处理错误,例如渲染提示信息、提供重试选项、禁用受影响的功能或提供其他降级状态。以下占位示例会抛出错误,因为它们未定义自定义恢复UI。
路由组件也可以调用
useIms()
useCommerce()
来访问Commerce REST API或直接从主机获取数据。两者均返回
{ data, error }
;处理完
error
后,从
useIms()
读取
data.imsToken
data.imsOrgId
,从
useCommerce()
读取
data.commerceHost
。生成的
Welcome
组件展示了
useIms()
的结果处理方式。仅当页面实际需要时才添加这些钩子。
示例——view类型mass action的占位页面及其路由注册:
jsx
// 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; // 非空字符串数组——选中的记录id
  const { close } = actions; // 完成后调用await close()(或actions.closeWithError())

  return (
    <main>
      <h1>Export customers</h1>
      <p>{selectedIds.length} selected</p>
    </main>
  );
}
jsx
// 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 /> }, // 保持索引路由在首位
    { path: "#/export-customers", element: <ExportCustomersPage /> }, // path === 配置中的`path`
  ],
});
对于order view button,将钩子替换为
useOrderViewButtonContext()
,处理完
error
后读取
data.orderId
——请查看order-view-buttons

Step 5 — Validate

步骤5 — 验证

Build the project to confirm the updated config is valid:
sh
aio app build
A build failure with a validation error points directly to the offending
adminUi
field.
构建项目以确认更新后的配置有效:
sh
aio app build
如果构建失败并出现验证错误,会直接指向有问题的
adminUi
字段。

Common Issues

常见问题

  • view
    vs
    worker
    mismatch
    : each variant is a strict object — a
    worker
    entry requires
    runtimeAction
    (and rejects
    path
    /
    sandboxPermissions
    ); a
    view
    entry requires
    path
    (and rejects
    runtimeAction
    /
    timeout
    ). They are discriminated by
    type
    .
  • Grid columns are worker-only: there is no
    view
    grid column. Only
    order
    ,
    product
    , and
    customer
    support
    gridColumns
    , and only
    order
    supports
    viewButtons
    .
  • runtimeAction
    with no handler
    : a worker entry whose
    <package>/<action>
    is not declared under
    runtimeManifest
    in
    src/commerce-backend-ui-2/ext.config.yaml
    leaves the generated
    workerProcess
    reference unresolved at deploy.
  • Wrong action location: handler sources and their
    runtimeManifest
    entry belong in the Admin UI extension folder
    src/commerce-backend-ui-2/
    — not
    src/commerce-extensibility-1/
    (where webhook and event handlers live). The
    function
    path is relative to
    src/commerce-backend-ui-2/
    .
  • Grid row keys must match column ids: keys in the
    okGridResponse
    rows must equal the
    id
    s in
    gridColumns.columns
    , or cells render empty (or fall back to the defaults bag).
  • View route
    path
    : register the
    { path }
    in
    src/app.jsx
    as the exact same string as the entry's config
    path
    — copy it verbatim, hash included, so the two line up.
  • Menu
    id
    charset
    : the menu
    id
    allows only letters, digits,
    /
    ,
    :
    , and
    _
    — no hyphens or spaces.
  • defineConfig
    not found
    : import
    defineConfig
    from
    @adobe/aio-commerce-lib-app/config
    .
  • Double renders/requests in development:
    createExtensionApp
    wraps the app in React
    <StrictMode>
    , so under
    aio app dev
    or
    aio app run
    components render twice and effects run an extra setup + cleanup cycle on mount. Duplicate renders or effect-triggered requests in development are expected StrictMode behavior, not a bug to fix; production builds are unaffected.
  • view
    worker
    不匹配
    :每个变体都是严格的对象——worker条目需要
    runtimeAction
    (且不允许
    path
    /
    sandboxPermissions
    );view条目需要
    path
    (且不允许
    runtimeAction
    /
    timeout
    )。它们通过
    type
    区分。
  • Grid columns仅支持worker变体:不存在view类型的grid column。仅
    order
    product
    customer
    支持
    gridColumns
    ,且仅
    order
    支持
    viewButtons
  • runtimeAction
    无对应处理程序
    :如果worker条目的
    <package>/<action>
    未在
    src/commerce-backend-ui-2/ext.config.yaml
    runtimeManifest
    下声明,会导致部署时生成的
    workerProcess
    引用无法解析。
  • 动作位置错误:处理程序源码及其
    runtimeManifest
    条目应放在Admin UI扩展文件夹
    src/commerce-backend-ui-2/
    中——而非
    src/commerce-extensibility-1/
    (webhook和事件处理程序所在位置)。
    function
    路径是相对于
    src/commerce-backend-ui-2/
    的。
  • 网格行键必须与列id匹配
    okGridResponse
    行中的键必须与
    gridColumns.columns
    中的
    id
    相等,否则单元格会显示为空(或回退到默认值)。
  • View路由
    path
    :在
    src/app.jsx
    中注册的
    { path }
    必须与条目配置中的
    path
    完全相同——直接复制该字符串,包括哈希符号,使两者保持一致。
  • Menu
    id
    字符集
    :Menu的
    id
    仅允许字母、数字、
    /
    :
    _
    ——不允许连字符或空格。
  • 找不到
    defineConfig
    :从
    @adobe/aio-commerce-lib-app/config
    导入
    defineConfig
  • 开发环境中重复渲染/请求
    createExtensionApp
    会将应用包裹在React
    <StrictMode>
    中,因此在
    aio app dev
    aio app run
    下,组件会渲染两次,副作用会在挂载时额外执行一次设置+清理周期。开发环境中的重复渲染或副作用触发的请求是StrictMode的预期行为,并非bug;生产构建不受影响。

Quality Bar

质量标准

  • aio app build
    completes without errors
  • Every worker
    runtimeAction
    has a matching action declared under
    runtimeManifest
    in
    src/commerce-backend-ui-2/ext.config.yaml
  • aio app build
    无错误完成
  • 每个worker类型的
    runtimeAction
    都在
    src/commerce-backend-ui-2/ext.config.yaml
    runtimeManifest
    下有对应的动作声明

Chaining

后续操作

After
aio app build
passes:
  • Add merchant settings — invoke
    commerce-app-business-config
    to expose configurable settings in Commerce Admin
  • Add webhook interceptors — invoke
    commerce-app-webhooks
    to intercept Commerce operations
  • Add event subscriptions — invoke
    commerce-app-eventing
    to subscribe to Commerce or external events
  • Add persistent storage — invoke
    commerce-app-storage
    to back worker actions with queryable DB storage
aio app build
通过后:
  • 添加商家设置——调用
    commerce-app-business-config
    以在Commerce Admin中公开可配置的设置
  • 添加webhook拦截器——调用
    commerce-app-webhooks
    以拦截Commerce操作
  • 添加事件订阅——调用
    commerce-app-eventing
    以订阅Commerce或外部事件
  • 添加持久化存储——调用
    commerce-app-storage
    为worker动作提供可查询的数据库存储支持

References

参考文档

  • references/grid-columns.md — Grid column config and handler contract
  • references/mass-actions.md — Mass action config (view and worker) and handler contract
  • references/order-view-buttons.md — Order view button config (view and worker) and handler contract
  • references/menu.md — Menu config and parent-menu constants
  • references/grid-columns.md — 网格列配置和处理程序契约
  • references/mass-actions.md — 批量操作配置(view和worker)和处理程序契约
  • references/order-view-buttons.md — 订单视图按钮配置(view和worker)和处理程序契约
  • references/menu.md — 菜单配置和父菜单常量