@adobedjangir/commerce-admin-management
v1.0.29
Published
Schema-driven system configuration for Adobe Commerce App Builder sync apps. Magento-style scoped config in Adobe App Builder Database (ABDB) with encryption, Commerce REST helpers, and React Admin UI.
Maintainers
Readme
@adobedjangir/commerce-admin-management — Developer Guide
A turnkey Adobe App Builder package for schema-driven system configuration in Adobe Commerce extensions. Provides encrypted scoped storage, a React-Spectrum admin UI mounted inside the Commerce Admin, OAuth1a helpers, import/export, and an extension point for host apps to add their own pages and actions.
Table of contents
- What you get
- Quick start
- What
npm installscaffolds for you - Required
.envvalues - Architecture
- Built-in actions
- Storage model
- Encryption
- Commerce connection flow
- Adding a host page (UI tab)
- Adding a host action (backend)
- Reusable helpers exported by the package
- Configuration override matrix
- Common operations
- Troubleshooting
- Optional add-ons
- Commerce connection types
- SaaS / App Management
What you get
| Capability | Where it lives |
| ------------------------------------------------------------------------------- | --------------------------------------------------- |
| Schema-driven config form (sections / groups / fields) | UI: SystemConfig page (built-in tab) |
| Per-scope values (default → websites → stores) with Magento-style inheritance | Action: system-config-list / system-config-save |
| AES-256-GCM encryption of sensitive fields at rest | system-config-crypto module |
| Adobe Commerce REST/OAuth1a client helper | commerce-creds module |
| First-run setup wizard for Commerce credentials (saved encrypted in ABDB) | UI: CommerceSetupWizard |
| Bulk JSON export/import with cross-environment website/store id remapping | Actions: export-config, import-config |
| App Builder Database (ABDB) auto-provisioning on deploy | ext.config.yaml |
| Host-extensible nav and pages | configureWeb({ extraNav, extraPages }) |
| Auto-scaffolded host scaffold on npm install | scripts/setup.js (postinstall hook) |
Quick start
# 1. Create a new App Builder project (skip if you already have one)
aio app init my-commerce-admin
cd my-commerce-admin
# 2. Install the package — postinstall scaffolds everything
npm install --save \
@adobedjangir/commerce-admin-management@latest \
@adobedjangir/commerce-admin-get-config@latest
npx commerce-admin-management-setup
npm install
# 3. Fill in .env (see required values below)
cp env.dist .env
$EDITOR .env
# 4. Deploy. ABDB is auto-provisioned; 11 actions + web assets ship to CDN.
aio app deployOpen the URL the deploy prints. First run shows the Commerce setup wizard; enter your store URL + OAuth1a creds, click Test connection, then Save & continue. The rest of the UI is then unlocked.
For local dev:
aio app run
# → https://localhost:9080What npm install scaffolds for you
PaaS integration uses Admin UI SDK v2 (
commerce/backend-ui/2). The package registers via the declarativeapp.commerce.config.ts(adminUi.menu) and the@adobe/aio-commerce-lib-apppre-app-build hooks — it no longer ships a hand-writtencommerce/backend-ui/1registration action. v2 mandates **React 19
- Spectrum S2** (the installer bumps these); the management UI (Spectrum S1) runs wrapped inside the v2
createExtensionAppshell. A single Admin menu opens the app; the SPA routes internally (System Config, Schema Editor, add-on pages).
The package's postinstall runs scripts/setup.js, which idempotently
materializes a host scaffold:
| Path | First install | Subsequent installs |
| ------------------------------------------------- | ----------------------------------------------------------------------------- | ------------------------------------------------------ |
| app.config.yaml | Wires commerce/backend-ui/2 + commerce/extensibility/1 $includes (drops any legacy backend-ui/1) | Idempotent; preserves sibling extensions |
| app.commerce.config.ts | Writes the v2 config (metadata + adminUi.menu), titles from .env | Never overwritten (flags if it lacks adminUi.menu) |
| src/commerce-backend-ui-2/ext.config.yaml | Menu view + core actions ($include core-package.yaml) + pre-app-build hook | Kept current (package-owned) |
| src/commerce-backend-ui-2/web-src/src/app.jsx | The v2 createExtensionApp bootstrap (wraps the S1 SPA) | Re-synced while the marker is present |
| src/commerce-backend-ui-2/web-src/src/nav.json | Starter nav entry → #/welcome | Never overwritten |
| src/commerce-backend-ui-2/web-src/src/pages/ | Page registry + Welcome.jsx starter | Never overwritten |
| src/commerce-backend-ui-2/web-src/src/addons.js | Host-owned add-on registry (audit-log / ims-access append here) | Never overwritten |
| src/commerce-extensibility-1/ext.config.yaml | App Management install lifecycle (actions generated to .generated/ at build) | Kept current (package-owned) |
| install.yaml, host package.json postinstall | App Management install manifest + aio-commerce-lib-app hooks postinstall | Idempotent |
The "never overwritten" rule means you can edit those files freely; upgrading the package will not lose your changes. The
.generated/lifecycle actions and the#app.commerce.configalias are produced by the pre-app-build hooks ataio app build— nogeneratestep runs at install (Windows-safe).
The package also runs scripts/build-web.js, producing
node_modules/@adobedjangir/commerce-admin-management/web/dist/index.{js,css} so the host
bundle has a ready-to-consume artifact.
Required .env values
| Variable | Purpose |
| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| OAUTH_CLIENT_ID | Adobe IMS Server-to-Server credentials — for ABDB token |
| OAUTH_CLIENT_SECRET | same |
| OAUTH_ORG_ID | same |
| OAUTH_SCOPES | should include AdobeID, openid, read_organizations, additional_info.projectedProductContext, additional_info.roles, adobeio_api, read_client_secret, manage_client_secrets, event_receiver_api, commerce.accs |
| AIO_DB_REGION | One of amer |
| SYSTEM_CONFIG_CRYPT_KEY| At least 8 chars. **Generate once with**openssl rand -base64 32` and never rotate — rotating breaks every encrypted value already in ABDB. |
Commerce REST creds (COMMERCE_BASE_URL, COMMERCE_CONSUMER_KEY, …) are
no longer set via .env. They are entered in the in-app wizard and
encrypted into ABDB under default/_system/commerce/connection.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ Adobe Commerce Admin ← iframe ← Experience Cloud Shell │
└─────────────────────────────────────────────────────────────┘
│
▼
https://<workspace>.adobeio-static.net
│
▼
React app (@adobedjangir/commerce-admin-management/web/dist/index.js)
│
┌───────────────┼───────────────┐
▼ ▼ ▼
HashRouter AppSectionNav MainPage (gated on connection status)
│
▼
getNavItems() + getPageComponent(id)
│
▼
<Page runtime ims> ─→ callAction(props, key, op, body)
│
▼
POST /api/v1/web/<package>/<action>
│
▼
OpenWhisk action handlers
│
┌───────────────┼───────────────┐
▼ ▼ ▼
ABDB Commerce REST/OAuth1a IMS
(system_config_data, system_config_schema, _system/*)Built-in actions
All under runtime package **CommerceAdminManagement**.
| Action | Verb | Description |
| ----------------------------------- | ---- | ------------------------------------------------------------------------------------------------ |
| commerce-rest-get | POST | Proxy any Commerce REST GET via OAuth1a using stored creds |
| commerce-connection-status | POST | { configured: bool, creds: masked } |
| commerce-connection-test | POST | Verifies form values OR saved creds against store/storeConfigs |
| commerce-connection-save | POST | Tests then encrypts + upserts creds in ABDB |
| system-config-list | POST | Reads paths with Magento-style scope inheritance, decrypts sensitive fields |
| system-config-save | POST | Upserts values, encrypts sensitive paths |
| system-config-schema | POST | CRUD on the schema document |
| export-config | POST | Dumps schema + values (sensitive decrypted) as portable JSON |
| import-config | POST | Re-imports a dump, re-encrypts with the target's key, remaps website/store ids via Commerce REST |
| sync-store-mappings-from-commerce | POST | Refreshes the general/settings/store_mappings blob from Commerce |
All actions are require-adobe-auth: false and include-ims-credentials: true,
so they receive the host's OAuth credentials via env-injection — they don't
require a user IMS token.
Storage model
ABDB collection system_config_data — one document per (scope, scope_id, path):
{
"_id": "sysconfig__default__0__campaign_general__url__url",
"scope": "default",
"scope_id": "0",
"path": "campaign_general/url/url",
"value": "https://example.com",
"createdAt": "2026-01-04T...",
"updatedAt": "2026-06-19T..."
}_idissysconfig__<scope>__<scopeId>__<path-with-/-as-__>- Inheritance chain:
stores:<storeId> → websites:<websiteId> → default:0 - Sensitive
values carry the prefixenc:v1:<salt>:<iv>:<tag>:<ct>(all base64url) — see Encryption.
Schema is stored separately in system_config_schema (single doc _id: 'v1').
Encryption
SYSTEM_CONFIG_CRYPT_KEY is the master secret for AES-256-GCM.
const { encrypt, decrypt, isEncrypted } = require('@adobedjangir/commerce-admin-management/crypto')
encrypt('hello', params) // → 'enc:v1:...:...:...:...'
decrypt('enc:v1:...', params) // → 'hello'
isEncrypted('enc:v1:...') // → true- Per-record
salt⇒ same plaintext under same key produces a different ciphertext each save (no oracle attacks). - Wire format is versioned (
v1) so the algorithm can be rotated without breaking existing values. - A fallback to
OAUTH_CLIENT_SECRETexists for first-touch convenience but is strongly discouraged in production — your IMS client secret rotates; your at-rest data must not.
Commerce connection flow
First load ──→ GET /commerce-connection-status {configured: false}
│
▼
CommerceSetupWizard
│
User fills form ──→ POST /commerce-connection-test {ok: true|false}
User clicks Save ──→ POST /commerce-connection-save
│
▼ encrypt(JSON.stringify(creds), SYSTEM_CONFIG_CRYPT_KEY)
│ upsert ABDB at default/_system/commerce/connection
▼
App unlocks → System Config tab visibleAny action that needs Commerce REST calls
getCommerceCreds(params, logger) or
getStoredCommerceOauthClient(params, logger), which read+decrypt the blob.
A 5-minute in-process cache avoids repeating the lookup per request.
A Reconfigure Commerce button in the top-right of the nav lets operators edit/replace the creds later.
Adding a host page (UI tab)
Three edits, no rebuild of the package:
1. Create the page
// web-src/src/pages/Orders.js
import React from 'react'
import { View, Heading } from '@adobe/react-spectrum'
export default function Orders ({ runtime, ims }) {
return (
<View padding="size-400">
<Heading level={2}>Orders</Heading>
</View>
)
}2. Register it
// web-src/src/pages/index.js
import Welcome from './Welcome'
import Orders from './Orders'
const pages = {
welcome: Welcome,
orders: Orders
}
export default pages3. Add a nav entry
// web-src/src/nav.json
{
"items": [
{ "id": "welcome", "path": "/welcome", "label": "Welcome", "icon": "Folder" },
{ "id": "orders", "path": "/orders", "label": "Orders", "icon": "ShoppingCart" }
]
}aio app run → the tab appears. The page receives { runtime, ims } as
props for use with callAction.
Icon names come from a Spectrum-icon registry in
[web/src/nav-icons.js](web/src/nav-icons.js). Built-in: Settings,
Properties, Data, User, ShoppingCart, Box, Folder. To add more,
extend that file and rebuild the package (node scripts/build-web.js).
Override a built-in page
If you want to replace the built-in System Configurations with your own version, just put an entry with the same id in your host registry:
const pages = { 'system-config': MyOwnSystemConfig, ... }The host always wins on id collision.
Adding a host action (backend)
Adobe App Builder merges application.runtimeManifest from app.config.yaml
with the extension's manifest, so host-defined actions live next to the
package's without any wiring step.
1. Write the action
// actions/reports-list/index.js
const { Core } = require('@adobe/aio-sdk')
const { errorResponse } = require('@adobedjangir/commerce-admin-management/actions/utils')
const {
getStoredCommerceOauthClient
} = require('@adobedjangir/commerce-admin-management/actions/commerce-creds')
async function main (params) {
const logger = Core.Logger('reports-list', { level: params.LOG_LEVEL || 'info' })
try {
const oauth = await getStoredCommerceOauthClient(params, logger)
const orders = await oauth.get('orders?searchCriteria[pageSize]=10')
return { statusCode: 200, body: { ok: true, items: orders.items || [] } }
} catch (e) {
if (e.code === 'COMMERCE_NOT_CONFIGURED') {
return errorResponse(412, e.message, logger)
}
return errorResponse(500, e.message, logger)
}
}
exports.main = main2. Declare it in app.config.yaml
application:
runtimeManifest:
packages:
HostActions:
license: Apache-2.0
actions:
reports-list:
function: actions/reports-list/index.js
web: 'yes'
runtime: 'nodejs:20'
inputs:
LOG_LEVEL: debug
SYSTEM_CONFIG_CRYPT_KEY: $SYSTEM_CONFIG_CRYPT_KEY
OAUTH_CLIENT_ID: $OAUTH_CLIENT_ID
OAUTH_CLIENT_SECRET: $OAUTH_CLIENT_SECRET
OAUTH_ORG_ID: $OAUTH_ORG_ID
OAUTH_SCOPES: $OAUTH_SCOPES
AIO_DB_REGION: $AIO_DB_REGION
annotations:
require-adobe-auth: false
include-ims-credentials: true
final: trueaio app deploy ships 11 package actions + your HostActions/reports-list
together. The action URL appears in web-src/src/config.json automatically.
3. Call it from a host page
import { callAction } from '@adobedjangir/commerce-admin-management/web'
// inside the component:
const r = await callAction({ runtime, ims }, 'HostActions/reports-list', '', { /* body */ })
console.log(r.body)callAction(props, key, operation, body) POSTs to the URL stored under
key in web-src/src/config.json, adds Bearer + x-gw-ims-org-id
headers, parses the JSON response, and surfaces non-2xx as thrown errors
with err.status and err.response.
Reusable helpers exported by the package
Backend (Node)
// ABDB client + helpers
const {
getClient, // → { client, close } using IMS S2S creds
withDbClient, // run a function with an auto-closed client
ensureImportCollectionsExist
} = require('@adobedjangir/commerce-admin-management/abdb')
// Magento-style scope helpers
const {
toStateKey, // (scope, scopeId, path) → ABDB _id
isValidPath,
normalizeScope,
normalizeScopeId,
buildInheritanceChain,
SENSITIVE_PLACEHOLDER
} = require('@adobedjangir/commerce-admin-management/shared')
// Per-record AES-256-GCM
const {
encrypt,
decrypt,
isEncrypted
} = require('@adobedjangir/commerce-admin-management/crypto')
// Commerce REST/OAuth1a
const {
getOauthClient,
getCommerceOauthClient
} = require('@adobedjangir/commerce-admin-management/oauth1a')
// Commerce credential storage (stored encrypted in ABDB)
const {
readCommerceCreds,
writeCommerceCreds,
testCommerceConnection,
getCommerceCreds,
getStoredCommerceOauthClient
} = require('@adobedjangir/commerce-admin-management/actions/commerce-creds')
// Schema-driven config lookup at runtime (used by external sync actions)
const { getConfig } = require('@adobedjangir/commerce-admin-management/config')
const value = await getConfig('campaign_general/url/url', params, { scope: 'stores', scopeCode: 'en_ch' })
// Top-level barrel:
const { getConfig, getClient, encrypt } = require('@adobedjangir/commerce-admin-management')Front-end (ESM)
import {
// App shell
CommerceAdminManagementApp, // full router + Spectrum provider
configureWeb, // pass actionUrls + extraNav + extraPages
// Components
MainPage,
SystemConfig,
SystemConfigSchemaEditor,
AppSectionNav,
// Registries
getNavItems,
getPageComponent,
NAV_ICONS,
getNavIcon,
BUILT_IN_PAGES,
// Hooks
useSystemConfig,
useSystemConfigSchema,
useConfirm,
// Utilities
callAction,
// Theme tokens (for styling host pages consistently)
THEME, PALETTE, RADIUS, SHADOW, SPACE, FONT
} from '@adobedjangir/commerce-admin-management/web'
import '@adobedjangir/commerce-admin-management/web/styles.css'Configuration override matrix
| Setting | Default lives in | Override location | Override mechanism |
| ---------------------------------- | --------------------------------------------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ABDB auto-provision | package ext.config.yaml (runtimeManifest.database.auto-provision: true) | host app.config.yaml | application: runtimeManifest: database: auto-provision: false |
| ABDB region | package ext.config.yaml (literal emea) | host app.config.yaml | application: runtimeManifest: database: region: amer (and update AIO_DB_REGION in .env to match) |
| Runtime IMS token | computed by aio-lib-core-auth | n/a | The action runtime injects via include-ims-credentials: true |
| Action keys (URL paths) | web/src/settings.js (DEFAULT_ACTION_KEYS) | host bootstrap | configureWeb({ actionKeys: { ... } }) |
| Built-in nav entries | package web/src/nav.json | host web-src/src/nav.json | extras merge after built-ins; host wins on id collision |
| Built-in pages | package web/src/pages/index.js | host web-src/src/pages/index.js | extras merge; host wins on id collision |
| Bootstrap (web-src/src/index.js) | package generator | own the file | Either remove the marker comment and import from @adobedjangir/commerce-admin-management/web, or edit freely while keeping the marker (loses on next install) |
Common operations
Rotate SYSTEM_CONFIG_CRYPT_KEY
Do not unless you've planned for re-encryption — every existing encrypted value becomes undecryptable. If you must:
- Export with the old key:
POST /export-config { schemaOnly: false }— the dump contains plaintext values for sensitive fields (v2+ dump format). - Update
SYSTEM_CONFIG_CRYPT_KEYin.envand redeploy. - Re-import the dump:
POST /import-config { dump, overwrite: true }— the importer re-encrypts sensitive paths with the new key.
For cross-environment migration (different key on each side), pass
sourceCryptKey: '<old-key>' to import-config so it can decrypt the
ciphertext with the old key before re-encrypting with the new one.
Change region
- Update
AIO_DB_REGIONin.env. - Update
region:literal underruntimeManifest.databaseinapp.config.yaml(host overrides the package default). - Ensure the workspace has App Builder Database entitled in that region in the Adobe Developer Console.
aio app deploy.
Promote a deploy
aio app deploy --workspace ProductionTargets the named workspace's database/region/region-bound creds.
SYSTEM_CONFIG_CRYPT_KEY should differ per environment; use
import-config with sourceCryptKey to migrate values.
Add an icon for use in nav.json
- Import the icon into
packages/commerce-admin-management/web/src/nav-icons.js - Add it to the
NAV_ICONSmap node packages/commerce-admin-management/scripts/build-web.js- Reference it in
nav.jsonas"icon": "<NewIconName>"
Re-link a local package during development
If you're hacking on the package and need the host to pick up changes:
cd packages/commerce-admin-management
node scripts/build-web.js
cd ../..
npm install ./packages/commerce-admin-management # refreshes node_modules link
rm -rf .parcel-cache dist web-src/dist
aio app runTroubleshooting
| Symptom | Likely cause | Fix |
| ------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Database not provisioned | App Builder Database not entitled on the workspace, or never ran aio app deploy since adding the package | Add the service in the Developer Console (region must match AIO_DB_REGION), then aio app deploy |
| Invalid region '$AIO_DB_REGION' | Tried to use $VAR substitution in runtimeManifest.database.region | aio substitutes $VAR only inside action inputs:. Use a literal region in YAML |
| Blank page at #/<anything> | Route table doesn't include a catch-all | Already fixed — App.js uses path="*". If you forked it, add <Route path="*" element={…}> |
| Stuck on "Checking Commerce connection…" forever | Running outside Experience Cloud iframe; @adobe/uix-guest's attach() was blocking | Already fixed — MainPage's attach() now races a 2-second timeout |
| Reports tab blank but System Configurations works | pages/index.js used CommonJS require('./X').default interop | Use ES import X from './X' instead |
| Nav button overflows below tabs | CSS not picked up | Make sure the package's dist/index.css is loaded (the bootstrap imports it automatically) |
| Encryption errors after env change | SYSTEM_CONFIG_CRYPT_KEY was rotated and there are old enc:v1: values | Either restore the old key, or perform the export/import re-encryption flow above |
| npm install overwrote my bootstrap | Marker comment still present | Remove the marker comment line, and ensure your file imports from @adobedjangir/commerce-admin-management/web so setup recognizes host-managed mode |
File reference
packages/commerce-admin-management/
├── DEVELOPER.md ← you are here
├── README.md ← short overview
├── package.json
├── src/ ← thin re-exports from @adobedjangir/commerce-admin-get-config
│ ├── abdb-helper.js
│ ├── abdb-config.js
│ ├── system-config-crypto.js
│ ├── system-config-shared.js
│ └── oauth1a.js
├── actions/
│ ├── utils.js
│ ├── commerce-creds.js ← read/write/test encrypted creds in ABDB
│ └── configurations/
│ ├── ext.config.yaml ← runtime manifest (database + 11 actions)
│ ├── index.html
│ ├── registration/index.js
│ ├── commerce/index.js
│ ├── commerce-connection-status/index.js
│ ├── commerce-connection-test/index.js
│ ├── commerce-connection-save/index.js
│ ├── system-config-list/index.js
│ ├── system-config-save/index.js
│ ├── system-config-schema/index.js
│ ├── export-config/index.js
│ ├── import-config/index.js
│ └── sync-store-mappings-from-commerce/index.js
├── web/
│ ├── index.js ← re-exports dist/
│ ├── styles.css ← flat fallback
│ ├── dist/ ← built by scripts/build-web.js
│ └── src/
│ ├── index.js ← package entry
│ ├── nav.json ← built-in nav entries
│ ├── nav-icons.js ← string-name → Spectrum icon map
│ ├── settings.js ← actionKeys, extensionId, configureWeb, registries
│ ├── theme.js
│ ├── utils.js ← callAction
│ ├── styles/index.css
│ ├── pages/index.js ← built-in page registry
│ ├── components/
│ │ ├── App.js ← router + Provider + ErrorBoundary
│ │ ├── MainPage.js ← gates app on Commerce connection
│ │ ├── ExtensionRegistration.js ← UIX guest registration
│ │ ├── AppSectionNav.js ← top nav from registry
│ │ ├── CommerceSetupWizard.js ← first-run wizard
│ │ ├── SystemConfig.js ← main config form
│ │ └── SystemConfigSchemaEditor.js ← schema editor
│ ├── hooks/
│ ├── schema/systemConfigSchema.js
│ └── utils/storeMappingsFromCommerceRest.js
└── scripts/
├── build-web.js ← esbuild bundle
└── setup.js ← postinstall scaffoldingOptional add-ons
The core package is a platform. Install any of these to layer on features — each
auto-registers its nav tabs, pages, and actions on npm install (via the
discovery mechanism), then activates on aio app deploy:
| Package | Adds |
|---|---|
| @adobedjangir/commerce-admin-audit-log | Records every config change (old→new/actor) + an Audit Log tab with filter & revert |
| @adobedjangir/commerce-admin-snapshots | Point-in-time Snapshots of schema + values, with safe wholesale restore |
| @adobedjangir/commerce-admin-ims-access | Email-based RBAC (admin/editor/viewer): Access Management UI, My Access page, nav badge, and server-side enforcement across the suite |
npm install @adobedjangir/commerce-admin-audit-log \
@adobedjangir/commerce-admin-snapshots \
@adobedjangir/commerce-admin-ims-access
aio app deploy # required — bundles each add-on's server hooks into the core actionsRBAC note: without commerce-admin-ims-access, the app is open (every caller
is effectively admin). Installing it turns on roles — set the bootstrap
super-admin(s) in .env:
[email protected]Roles: viewer (read-only) · editor (save config values) · admin (schema,
audit revert, snapshots, access management). Enforcement is server-side (in the
actions) and in the UI.
Action authorization
Every action is gated server-side via the shared requireRole / requireValidToken
helpers (actions/utils.js). Enforcement requires the
commerce-admin-ims-access
add-on — without it there is no role system, so the app runs open
(single-tenant); the gates return "allowed". require-adobe-auth: false on an
action does not mean "unprotected" — protection is the role gate below.
| Action | Min role | Notes |
|---|---|---|
| export-config | admin (fail-closed) | decrypts + returns plaintext config incl. secrets |
| commerce-connection-save | admin (fail-closed) | sets the Commerce instance + creds for the whole app |
| system-config-schema (save/reset/import) | admin | schema edits |
| import-config | editor | mass write |
| system-config-save / -bulk-save | editor | per-field requiredRole also enforced (server-resolved) |
| sync-store-mappings-from-commerce | editor | writes store mappings |
| commerce-connection-test | editor | probes endpoints with supplied creds |
| commerce-rest-get | valid IMS token | proxies Commerce REST with server creds — token validated against IMS |
| system-config-list | viewer | read |
| commerce-connection-status | viewer | read (gates the app UI) |
| commerce-rest-get (read) | valid token | see above |
Add-on actions (when installed): audit-delete, snapshot create/restore/delete,
and access-save/access-delete are admin; audit-list, snapshot-list,
access-list require the caller be resolvable. Any new write/export action
must add a requireRole/requireValidToken gate.
Fail-open vs fail-closed: gates fail open on a transient role-resolution
error (availability) except credential export/write (export-config,
commerce-connection-save), which fail closed.
Commerce connection types
The first-run wizard supports both:
- PaaS / on-prem Adobe Commerce (Magento) — OAuth1a (consumer key/secret + access token/secret), signed per request.
- SaaS / Adobe Commerce as a Cloud Service (ACCS) — IMS server-to-server
bearer (
commerce.accsscope) minted per request from the workspaceOAUTH_*credentials, sent withx-api-key+x-gw-ims-org-id. REST calls hit<api-host>/<tenant>/V1/…(norest/<store>/prefix). The ACCS API consumer's role must grant the resources you use.
SaaS / App Management
Adobe is retiring Manual Extensions Selection for SaaS (ACCS) in favor of
Commerce App Management. PaaS is unaffected — nothing to do there. This app
supports both, selected by one .env flag:
COMMERCE_PLATFORM=paas # default — classic Manual Extensions Selection
# COMMERCE_PLATFORM=saas # additionally register with Commerce App ManagementHow the two platforms differ (Admin UI SDK registration):
| | PaaS | SaaS / App Management |
|---|---|---|
| Menu registration | hand-written registration action (Manual Extensions Selection) | generated registration action from app.commerce.config → adminUiSdk.registration |
| commerce/backend-ui/1 | our $include (registration + web + 11 core actions) | generated (src/commerce-backend-ui-1/): registration action + web: web-src |
| Core backend actions | under commerce/backend-ui/1 | under application.runtimeManifest.packages.CommerceAdminManagement |
| App discovery | Manual Extensions Selection | commerce/extensibility/1 + app.commerce.config metadata |
Both platforms use the same registration behavior: menu titles come from
APP_TITLE / APP_SECTION_TITLE and the page title from APP_PAGE_TITLE, read
at runtime from action inputs, and the registration includes page.title.
PaaS uses actions/configurations/registration/index.js; SaaS uses
actions/configurations/registration-saas/index.js (same logic, built on the
lib's registrationRuntimeAction). We deliberately serve our own env-driven
registration action rather than the lib-generated one — the generated action
bakes static titles at build time and its schema has no page field.
app.commerce.config still declares adminUiSdk.registration as App Management
install metadata.
The 11 core action definitions live in one shared fragment
(actions/configurations/core-package.yaml) that both layouts $include, so the
actions — and their /api/v1/web/CommerceAdminManagement/<action> URLs — are
identical on both platforms. The React SPA and add-on packages (audit-log,
ims-access, snapshots) are unchanged. Only the menu registration mechanism and
which extension owns the core actions differ. PaaS is untouched.
Enabling SaaS — one command:
Run the setup CLI and answer saas when prompted:
npx commerce-admin-management-setup
# Which Adobe Commerce platform is this app for? [paas/saas] (default: paas): saas
# App title — Commerce admin menu label (default: Configuration Management): …
# Parent section label the menu sits under (default: Apps): …
# In-app page/tab title (default: <App title>): …The CLI also prompts (paas and saas) for the three per-project titles and
writes them to .env — APP_TITLE, APP_SECTION_TITLE, APP_PAGE_TITLE.
Press Enter to keep the shown default (which pre-fills from any current .env
value on a re-run). Skip individual prompts non-interactively with flags:
--app-title=…, --app-section-title=…, --app-page-title=….
commerce-admin-management-setupis the bin shipped by the@adobedjangir/commerce-admin-managementpackage (already in yournode_modules), so run it unscoped —npxresolves it fromnode_modules/.bin. (Don't runnpx @adobedjangir/commerce-admin-management-setup— that would try to install a package by that name, which doesn't exist.)
Or skip the prompt with a flag / env var:
npx commerce-admin-management-setup --saas
# or: COMMERCE_PLATFORM=saas npx commerce-admin-management-setupFor saas this does everything for you:
- Writes
COMMERCE_PLATFORM=saasto.env. - Seeds
app.commerce.config.ts— metadata + anadminUiSdk.registrationblock whosemenuItemsmirror the PaaS registration action (same ids; titles fromAPP_TITLE/APP_SECTION_TITLE). - Installs the App Management deps (via
npm install, through a shell) and runsnpx @adobe/aio-commerce-lib-app generate allto create thecommerce/extensibility/1ext.config + runtime actions + manifest, then addsinstall.yamland thepostinstallhook. (We deliberately do not use the lib'sinitcommand: its dependency-install step callsspawnSync('npm')without a shell, which throwsspawnSync npm ENOENTon Windows.npm installvia a shell +generate allavoids that, so this works on Windows.) - Rewires
app.config.yamlto the SaaS extension layout: addscommerce/extensibility/1;commerce/backend-ui/1→ ourbackend-ui.saas.yamlfragment (env-driven registration action + web-src); the coreCommerceAdminManagementpackage + thedatabaseblock move underapplication.runtimeManifest; and the addon discovery hook moves toapplication.hooks.pre-app-build. (Idempotent, done with theyamldocument API — re-running is a safe no-op.)
The only thing left for you to run is the deploy:
aio app build --force-build && aio app deploy --force-deploy --no-buildThe app then appears under Apps → App Management in the Commerce admin.
Requires admin-ui-sdk ≥ 3.3.1 on the Commerce instance.
The full SaaS wiring (including
init) runs only for an explicitcommerce-admin-management-setupinvocation — never silently during a plainnpm install. Apaasanswer (the default) changes nothing beyond recordingCOMMERCE_PLATFORM=paas.
License
Apache-2.0 © Adobe Inc.
