commerce-app-admin-ui について
commerce-app-admin-uiは、Adobe CommerceアプリのAdmin UI拡張を設定するスキルです。アプリ設定と生成される拡張ひな形を使い、グリッド列、アクション、注文ボタン、メニュー、カスタムACLリソースを追加できます。
主な機能
- 01注文、商品、顧客のグリッド列を設定
- 02一括アクションと注文表示ボタンを追加
- 03Admin UIのメニュー項目を追加
- 04カスタムACLリソースを宣言
- 05フロントエンドのルートとワーカーハンドラーを生成
活用シーン
- 01Commerce管理グリッドに独自データを追加
- 02管理対象の一括操作を作成
- 03Admin UIメニューからアプリ画面を開く
- 04アプリ固有の管理権限を定義
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ adobe/ skills --skill commerce-app-admin-uiインストール後、Claude Code で/commerce-app-admin-uiを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
evals/evals.jsonreferences/custom-acl.mdgrid-columns.mdmass-actions.mdmenu.mdorder-view-buttons.md詳細
Commerce アプリの管理 UI を設定する
既存の app.commerce.config.ts の adminUi ブロックを追加または変更します。
Admin UI 拡張ポイント(commerce/backend-ui/2)を使用すると、Commerce アプリからカスタムグリッド列、一括操作、注文ビューのボタン、メニュー項目、カスタム ACL リソースを Commerce Admin に追加できます。
その他の拡張領域(webhook、イベント、ビジネス設定)は、それぞれ専用のスキルで個別に追加します。
前提条件
- 設定ファイルが存在するだけでなく、アプリのスキャフォールディングと初期化が完了していることを確認します。以下の両方が必要です。
- プロジェクトのルートに
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 で作成できるのは、TypeScript Commerce 設定用に
initがスキャフォールディングする TypeScript ビルド設定(webpack-config.cjsとルートのtsconfig.json)がプロジェクトにある場合のみです。詳細はcommerce-app-initを参照してください。それ以外の場合は、アクションを JavaScript で作成します。
拡張ポイントの概要
| 拡張ポイント | エンティティ | バリアント | サーバーハンドラー | リファレンス |
|---|---|---|---|---|
| グリッド列 | 注文、商品、顧客 | worker のみ | はい | grid-columns |
| 一括操作 | 注文、商品、顧客 | view / worker | worker のみ | mass-actions |
| 注文ビューのボタン | 注文のみ | view / worker | worker のみ | order-view-buttons |
| メニュー | 単一項目(adminUi.menu) | view(iframe) | いいえ | menu |
| カスタム ACL | リスト(adminUi.acl) | なし | いいえ | custom-acl |
view は、エントリーの path にあるアプリの Web UI(web-src)を iframe として表示します。worker はランタイムアクションをサーバー側で呼び出します。
カスタム ACL リソースにはバリアントもハンドラーもありません。Commerce がユーザーロールのツリーに表示する独立した権限であり、アプリ自身が確認します(custom-acl を参照)。
グリッド列は常に worker です。メニューは常に iframe です。
ステップ 1 — 意図を把握する
ユーザーが追加したい各項目について、次の情報を収集します。
- 拡張ポイント — グリッド列、一括操作、注文ビューのボタン、メニュー、カスタム ACL リソースのいずれか
- エンティティ —
order、product、customerのいずれか(グリッド列と一括操作の場合。ビューのボタンは注文のみ。メニューとカスタム ACL リソースにはエンティティがありません) - 一括操作とビューのボタンの場合は、バリアント —
worker(ランタイムアクション)またはview(web-srcの iframe) - 拡張ポイントのフィールド(列の定義、ボタンのラベル、メニューの親項目など)— すべてのフィールドについては、上の表にあるリファレンスファイルを参照してください
ステップ 2 — app.commerce.config.ts で宣言する
最上位の adminUi ブロックを追加するか、既存のブロックにマージし、他のすべてのドメインを保持します。すでに adminUi が存在する場合は、置き換えずにマージしてください。既存のエンティティ、メニュー、配列の既存エントリを保持します。
これらのフィールドは拡張ポイント間で共通です。
| フィールド | 制約 |
|---|---|
runtimeAction | <package>/<action>。宣言するハンドラーアクション(ステップ 4)を解決できる必要があります。 |
confirm | 任意の { title?, message? } — アクションの実行前に確認ダイアログを表示します。 |
notifications | 任意の { success?, error? } — 完了時に Commerce が表示するトーストテキストです。 |
sandboxPermissions | 任意の配列(view/iframe のエントリ)。空でないこと、重複がないこと。各項目は allow-downloads、allow-modals、allow-popups のいずれかです。 |
各機能を含む最小限の例です(必要な部分のみ使用してください)。
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" },
],
}
view のバリエーション(iframe)、および各拡張ポイントのフィールドと制約の全容については、記述する前に References にある該当するリファレンスファイルを確認してください。
ステップ 3 — 拡張ポイントを登録する
init を実行して、commerce/backend-ui/2 を app.config.yaml と install.yaml に追加し、src/commerce-backend-ui-2/ 拡張フォルダーを生成します。これはべき等なので、拡張機能がすでに登録されている場合でも安全に実行できます。
npx @adobe/aio-commerce-lib-app init
ビルドでは、adminUi 設定から拡張機能の ext.config.yaml が生成されます。各ワーカーの 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)が生成され、package.json に #web/* インポートエイリアスが追加されます。また、固定バージョンの react、react-dom、@react-spectrum/s2、@adobe/aio-commerce-lib-admin-ui(React と Spectrum S2 は admin-ui ライブラリのオプションのピア依存関係です)が宣言され、インストールされます。さらに、適切な TypeScript および Babel のサポート/設定のための devDependencies も追加されます。別の必須ファイルフェーズが生成のたびに実行され、既存バージョンを置き換えずに一連の Web ソースサポートファイルが存在することを確認します。現在の対象には web-src/.babelrc が含まれます。このファイルは各環境で React の自動 JSX 変換を選択し、開発ビルドでは JSX 診断を維持し、本番ビルドでは jsxDEV 呼び出しを出力しないようにします。BABEL_ENV が設定されている場合は、NODE_ENV と同期させてください。設定環境の選択では、Babel は BABEL_ENV を優先するためです。TypeScript のスキャフォールドでは、プロジェクトの合成された typecheck スクリプトに typecheck:web-src も追加されます。これらの依存関係について、異なるバージョンを独自に選ばないでください。互換性のないバージョンがすでにインストールされている場合、スキャフォールドは失敗します。
web-src がすでに存在するためスキャフォールドがスキップされた場合は、その package.json を確認し、@react-spectrum/s2 ではなく、従来の React Spectrum(@adobe/react-spectrum または s2 を含まない @react-spectrum/<component>)が使われていないか確認してください。両者には互換性がありますが、Adobe が移行を推奨しているのは S2 であり、このスキャフォールドも S2 を対象としています。アップグレードを提案してください。このスキルは Admin UI 拡張機能を設定するだけで、アップグレード自体は実行しません。実際の移行については、ユーザーに commerce-app-migrate スキルを案内してください。
npx skills add adobe/skills --skill commerce-app-migrate
ユーザーから依頼されない限り、インストールやアップグレードは自分で実行しないでください。
ステップ 4 — ハンドラーを実装する
実装内容はバリアントによって異なります。以下の例は TypeScript です。プロジェクトで JavaScript を使用している場合は、型のインポートと型注釈を省略してください。
ワーカーバリアント(グリッド列、ワーカーの一括アクション、ワーカーのビュー操作ボタン)
各ワーカーの runtimeAction には、Admin UI 拡張フォルダー src/commerce-backend-ui-2/ 内に宣言して実装するアクションが必要です。
- ハンドラーのソースを
src/commerce-backend-ui-2/actions/に配置します。 src/commerce-backend-ui-2/ext.config.yamlのruntimeManifest内にある独自のパッケージの下で宣言します(パッケージ名は任意です)。ビルドではこのファイルのhooksとoperationsが管理されますが、runtimeManifestの下に追加したパッケージは保持されるため、再ビルド後もアクションは残ります。
# 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
runtimeAction の <package>/<action> はそのまま対応します。my-app/order-grid → パッケージ my-app、アクション order-grid です。Commerce はこれらのワーカーアクションを Adobe IMS トークンを使って呼び出すため、require-adobe-auth: true(およびバインドされた入力を固定するための final: 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),
);
}
}
ビューのバリアント(ビューの一括アクション、ビューボタン、メニュー)
サーバーハンドラーはありません。Commerce は、エントリーの path にあるアプリの web-src を iframe で開きます。このフロントエンドはステップ 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 配列では、インデックスルートを先頭にする必要があります。
// 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 /> }],
});
ワーカーバリアントで runtimeAction をアクションに接続するのと同様に、各 view エントリーの path をページとルートに接続する必要があります。エントリーごとにその接続をスキャフォールドします。ただし、不足しているものだけを作成してください。既存のページやルートを上書きしたり変更したりしないでください。
メニューには path がありません。インデックスルート(src/pages/main-page.jsx)にレンダリングされます。このページはスキャフォールドによって、すでにプレーンなページとして作成されています。メニュー用にスキャフォールドするものはありません。main-page.jsx はそのまま残してください(必要であれば内容をカスタマイズできます)。設定の parentMenu には MENU_* 定数のみが必要です(menu を参照)。
path を持つ、view タイプの一括アクションと注文ビューボタンそれぞれについて、次の手順を行います。
-
ルートを確認します。
src/app.jsxで、pathがエントリーの設定pathと一致するroutesエントリーを探し、web-src/src/pages/で対応するページファイルを探します。そのpathのルートがすでに存在する場合は、ルートとページを変更せず、そのまま次へ進みます。 -
プレースホルダーページを作成します(不足している場合のみ)。
web-src/src/pages/<name>.jsxを追加します。アプリ設定が TypeScript の場合は、.tsxと TypeScript を使用します。生成されたmain-page/welcomeの見た目に合わせて、最小限の内容にします(見出しを含む<main>)。著作権ヘッダーは追加しないでください。生成されたweb-srcファイルにはヘッダーがありません。エントリータイプに応じたコンテキストフックをあらかじめ接続します(下の表を参照)。 -
src/app.jsxにルートを登録します(不足している場合のみ)。#web/pages/*エイリアスを使用してページをインポートし、routesに{ path, element }エントリーを追加します。pathは、先頭の#/を含め、設定のpathと完全に同じ文字列にしてください。ルートと設定エントリーが一目で対応するよう、文字列をそのままコピーします。インデックスルートは先頭に維持してください。
ビュータイプに応じて、次のフックをあらかじめ接続します。すべて @adobe/aio-commerce-lib-admin-ui/web からインポートします。
| ビューエントリー | コンテキストフック | 併用するもの | 参照 |
|---|---|---|---|
一括アクション(view) | useMassActionContext() → { data, error }。data.selectedIds を使用 | useHostConnection() → { actions, error }。actions.close() を使用 | mass-actions |
| 注文ビューボタン | useOrderViewButtonContext() → { data, error }。data.orderId を使用 | useHostConnection() → { actions, error }。actions.close() を使用 | order-view-buttons |
| メニュー | なし(プレーンなインデックスページ) | — | menu |
これらのフックは、エラーをスローする代わりに返します。ルートで、レンダリング中に返されたエラーをスローすると、SDK のエラーバウンダリーにエラーが送られ、拡張機能のコンテンツがフォールバック UI に置き換わります。ルートをマウントしたままにする必要がある場合は、メッセージを表示する、再試行を可能にする、影響を受ける機能を無効にする、または別の縮退状態を提供するなど、ローカルでエラーを処理してください。以下のプレースホルダー例では、独自のリカバリー UI を定義していないため、エラーをスローします。
ルートコンポーネントでは、Commerce REST API にアクセスするために useIms() を、ホストから直接データを取得するために useCommerce() を呼び出すこともできます。どちらも { data, error } を返します。error を処理した後、useIms() からは data.imsToken と data.imsOrgId を、useCommerce() からは data.commerceHost を読み取ります。生成された Welcome コンポーネントでは、useIms() の結果の処理方法を確認できます。これらのフックは、ページで実際に必要な場合にのみ追加してください。
例 — 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() に置き換え、error を処理した後に data.orderId を読み取ります。詳しくはorder-view-buttonsをご覧ください。
メニュー/表示ページから独自のアクションを呼び出す
メニューまたは表示ページでは、Commerce からデータを受け取るのではなく、独自のデータを取得する必要がある場合があります。たとえば、カスタムメニューページでテーブルを表示する場合です。その場合は、web-src からアプリ独自のアクションを直接呼び出します。
拡張機能で actions と web-src の両方を宣言すると、aio app build/aio app dev によって、各アクションの実際の URL を含む web-src/src/config.json(config.web.injectedConfig)が自動生成されます。このファイルは gitignore 対象で、ビルドのたびに再生成されるため、手動でひな形を作成する必要はありません。
// src/commerce-backend-ui-2/web-src/src/hooks/use-config.js
import { useCallback } from "react";
import actionUrls from "../config.json";
export function useConfig() {
return { getActionUrl: useCallback((action) => actionUrls[action], []) };
}
// Usage — combine with useIms() for the auth headers:
import { useIms } from "@adobe/aio-commerce-lib-admin-ui/web";
const { data, error } = useIms();
if (error) throw error;
const response = await fetch(getActionUrl("my-app/order-grid"), {
headers: {
authorization: `Bearer ${data.imsToken}`,
"x-gw-ims-org-id": data.imsOrgId, // required for require-adobe-auth: true actions, or the request fails
},
});
ステップ 5 — 検証
プロジェクトをビルドして、更新した設定が有効であることを確認します。
aio app build
ビルド時に検証エラーが発生した場合は、問題のある adminUi フィールドが直接示されます。
よくある問題
viewとworkerの不一致:各バリアントは厳密なオブジェクトです。workerエントリーにはruntimeActionが必要で、path/sandboxPermissionsは指定できません。viewエントリーにはpathが必要で、runtimeAction/timeoutは指定できません。両者はtypeで判別されます。- グリッド列は worker 専用:
viewのグリッド列はありません。gridColumnsをサポートするのはorder、product、customerのみで、viewButtonsをサポートするのはorderのみです。 - ハンドラーがない
runtimeAction:<package>/<action>がsrc/commerce-backend-ui-2/ext.config.yamlのruntimeManifestに宣言されていない worker エントリーは、デプロイ時に生成されたworkerProcessの参照が解決されない状態になります。 - アクションの配置場所が誤っている:ハンドラーのソースと
runtimeManifestのエントリーは、Admin UI 拡張機能フォルダーsrc/commerce-backend-ui-2/に置きます。Webhook やイベントのハンドラーを置くsrc/commerce-extensibility-1/ではありません。functionのパスはsrc/commerce-backend-ui-2/からの相対パスです。 - グリッドの行キーは列 ID と一致させる必要がある:
okGridResponseの行のキーはgridColumns.columnsのidと一致している必要があります。一致しない場合、セルは空で表示されるか、デフォルトの値の集合が使用されます。 - 表示ルートの
path:src/app.jsxに登録する{ path }は、エントリー設定のpathとまったく同じ文字列にしてください。ハッシュも含めてそのままコピーし、両者を一致させます。 - メニューの
idで使用できる文字:メニューのidで使用できるのは英字、数字、/、:、_のみです。ハイフンやスペースは使用できません。 defineConfigが見つからない:@adobe/aio-commerce-lib-app/configからdefineConfigをインポートしてください。- 開発中にレンダリング/リクエストが重複する:
aio app devおよびaio app runで提供されるローカルビルドは開発用ビルドです。createExtensionAppはアプリを React の<StrictMode>でラップするため、コンポーネントは 2 回レンダリングされ、マウント時のエフェクトはセットアップとクリーンアップを追加で 1 サイクル実行します。ローカル開発中にレンダリングが重複したり、エフェクトによるリクエストが重複したりするのは、StrictMode の想定された動作であり、修正すべきバグではありません。aio app buildとaio app deployでは、本番用ビルドが生成され、<StrictMode>はバンドルから除去されるため、ラップなしでレンダリングされます。
品質基準
aio app buildがエラーなく完了する- すべての worker
runtimeActionに対応するアクションが、src/commerce-backend-ui-2/ext.config.yamlのruntimeManifestに宣言されている
連携
aio app build が成功した後に、次の操作を行います。
- マーチャント設定の追加 —
commerce-app-business-configを呼び出して、Commerce Admin に設定可能な設定項目を公開します - Webhook インターセプターの追加 —
commerce-app-webhooksを呼び出して、Commerce の操作をインターセプトします - イベントサブスクリプションの追加 —
commerce-app-eventingを呼び出して、Commerce または外部のイベントをサブスクライブします - 永続ストレージの追加 —
commerce-app-storageを呼び出して、クエリ可能な DB ストレージでワーカーのアクションを支えます
参考資料
- references/grid-columns.md — グリッド列の設定とハンドラーの契約
- references/mass-actions.md — 一括アクションの設定(ビューとワーカー)とハンドラーの契約
- references/order-view-buttons.md — 注文ビューのボタン設定(ビューとワーカー)とハンドラーの契約
- references/menu.md — メニューの設定と親メニューの定数
- references/custom-acl.md — カスタム ACL リソースの設定と実行時の権限チェック