Configure Commerce App Eventing
Adds or modifies event sources — Commerce-native events or external events — in an existing app.commerce.config.ts.
Extensibility domains other than eventing (webhooks, 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.tspresent in the project root, and- the project initialized — signalled by the generated
src/commerce-extensibility-1/directory and installednode_modules(the@adobe/aio-commerce-lib-appdependency).
- If
app.commerce.config.tsis missing, stop and invokecommerce-app-initfirst (it writes the config, then runs init). - If the config is present but the project is not initialized (no
src/commerce-extensibility-1/ornode_modules), runnpx @adobe/aio-commerce-lib-app initbefore 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+ roottsconfig.json) thatinitscaffolds for a TypeScript Commerce config — seecommerce-app-init. Otherwise, author actions in JavaScript. - Ensure
CloudIntegrationSDK(I/O Events) andcommerceeventing(Adobe I/O Events for Adobe Commerce) are subscribed in the Developer Console workspace:List currently subscribed services:
aio console workspace api list --projectName <project> --workspaceName <workspace> --jsonIf either service is missing, re-subscribe with the full merged set of service codes (existing + missing).
aio console workspace api addreplaces the subscription list — omitting a currently-subscribed service will remove it.aio console workspace api add \ --projectName <project> \ --workspaceName <workspace> \ --service-code <existing-codes>,CloudIntegrationSDK,commerceeventing \ --jsonIf the command fails with "product profile required" for
commerceeventing, ask the user for the profile name and retry with--license-config commerceeventing=<profile>.
Step 1 — Understand intent
Ask whether the user wants to configure Commerce events, external events, or both:
- Commerce events (
eventing.commerce): native Commerce events. Names followplugin.<segments>orobserver.<segments>. - External events (
eventing.external): events from third-party systems (e.g., ERP, CRM). Names are free-form ([\w\-_.]+).
For each event source, gather:
- Provider label, description, and optional key
- For each event: name, label, description, and which runtime action(s) should handle it (format:
<package>/<action>) - For Commerce events only: fields to extract from the event payload (empty array captures the full payload), and any optional filter rules
- Optionally, which Commerce environments the event applies to (
env)
Step 2 — Derive config values
Apply the following validation rules before writing the config. Surface any issues to the user before proceeding.
| Field | Constraint |
|---|---|
| Commerce event name | Starts with plugin. or observer.; each segment matches [a-z_]+; max 180 chars |
| External event name | [\w\-_.]+; max 180 chars |
| Provider label | Max 100 chars |
| Provider description | Max 255 chars |
| Provider key | Optional; alphanumeric + hyphens only; max 50 chars |
| Event label | Max 100 chars |
| Event description | Max 255 chars |
| Field name | [a-zA-Z0-9_\-.[\]]+ or * |
| Rule operator | greaterThan, lessThan, equal, regex, in, or onChange |
| Runtime action | <package>/<action> (e.g., my-package/handle-order-placed) |
| Event env (optional) | Non-empty array of "paas" / "saas"; omitted = all environments |
Step 3 — Update app.commerce.config.ts
Add or merge eventing.commerce and/or eventing.external into the existing config, preserving all other domains. If the config already has an eventing key, extend it rather than replacing it.
Minimal example (Commerce event):
eventing: {
commerce: [{
provider: { label: "Commerce Events Provider", description: "..." },
events: [{
name: "plugin.order_placed", // plugin.<segments> or observer.<segments>
label: "Order Placed",
description: "Triggered when a customer places an order.",
fields: [{ name: "order_id" }], // empty array = full payload; Commerce events only
runtimeActions: ["my-package/handle-order-placed"], // <package>/<action>
}],
}],
}
See assets/eventing-config.ts for the full reference including external event sources.
Creating the handler action
For events that reference runtime actions via runtimeActions, create the action file under src/actions/ and register it in app.config.yaml.
Register the action
Add a user-defined package to src/commerce-extensibility-1/ext.config.yaml alongside the existing app-management package. Use any name except app-management (reserved by the framework):
# src/commerce-extensibility-1/ext.config.yaml
# (add below the auto-generated app-management package)
runtimeManifest:
packages:
app-management:
# ... auto-generated — do not edit
my-app: # your package name — any name except "app-management"
actions:
handle-order-placed:
function: actions/handle-order-placed/index.js # relative to src/commerce-extensibility-1/
web: "no"
runtime: nodejs:24
annotations:
require-adobe-auth: false
The <package>/<action> format in runtimeActions maps directly: my-app/handle-order-placed → package my-app, action handle-order-placed.
Handler skeleton
Event handlers receive a CloudEvents-shaped payload. params.data bundles the event payload together with metadata, so don't read fields directly off params.data: params.data.value is the event payload — the fields declared in the event's fields array (or the full payload if fields is empty); params.data._metadata is Commerce instance metadata; params.data.source is the merchant/environment ID pair configured in the Commerce eventing configuration.
Payload shape varies by event — check the Adobe Commerce events reference for the specific event name before deciding on a fields path. Some events nest the payload in a sub-object (e.g. value.order.entity_id); others put the fields flat on value (e.g. value.order_id), as in the example below.
// src/commerce-extensibility-1/actions/handle-order-placed/index.ts
export async function main(params: Record<string, unknown>) {
const data = params.data as Record<string, unknown>;
const value = data.value as Record<string, unknown>;
// value contains the fields declared in the event's `fields` array
// (or the full payload if fields is empty)
const orderId = value.order_id;
// process the event ...
return { statusCode: 200, body: { processed: true } };
}
Calling the Commerce REST API from a handler
If the handler needs to call the Commerce REST API (e.g. to fetch additional order data), use getCommerceClient from @adobe/aio-commerce-lib-app instead of reinventing config or env-based access to the Commerce instance (e.g. a custom .env variable such as AIO_COMMERCE_API_BASE_URL, or a business config field for the base URL). The SDK already stores the Commerce base URL and deployment type from the app's association, so getCommerceClient resolves them for you:
// src/commerce-extensibility-1/actions/handle-order-placed/index.ts
import { getCommerceClient } from "@adobe/aio-commerce-lib-app";
import { resolveImsAuthParams } from "@adobe/aio-commerce-lib-auth";
export async function main(params: Record<string, unknown>) {
const data = params.data as Record<string, unknown>;
const value = data.value as Record<string, unknown>;
const orderId = value.order_id;
const client = await getCommerceClient(resolveImsAuthParams(params));
const order = await client.get(`orders/${orderId}`).json();
// process the event using order details ...
return { statusCode: 200, body: { processed: true } };
}
The client's base URL already includes the REST prefix and API version (rest/<store>/V1 for PaaS, V1 for SaaS) — pass only the resource path (orders/${orderId}, not V1/orders/${orderId} or rest/all/V1/orders/${orderId}).
See Accessing the Associated Commerce Instance from Runtime Actions for the full pattern, including handling the unassociated state (AssociationRecordNotFoundError).
Step 4 — Validate
Build the project to confirm the updated config is valid:
aio app build
A build failure with a validation error points directly to the offending config field.
Common Issues
- External event has
fields: Thefieldsproperty is only valid on Commerce events; external events don't support it. runtimeActionsformat error: Must be<package>/<action>. Both parts are lowercase alphanumeric + hyphens only.app-managementpackage name conflict: The framework generates this package inext.config.yamlon every build. Use any other name for your own actions.- Function path is relative to
src/commerce-extensibility-1/: Do not usesrc/...or project-root-relative paths.actions/handle-order-placed/index.jsresolves correctly;src/commerce-extensibility-1/actions/handle-order-placed/index.jsdoes not. defineConfignot found: Ensure@adobe/aio-commerce-lib-appis installed anddefineConfigis imported from@adobe/aio-commerce-lib-app/config.- Build fails on missing action: A runtime action referenced in
runtimeActionsmust exist in the project. Check the action files undersrc/commerce-extensibility-1/actions/and create any missing stubs. - Handler needs the Commerce base URL: Use
getCommerceClient(@adobe/aio-commerce-lib-app), not a custom.envvariable or business config field. See Calling the Commerce REST API from a handler.
Quality Bar
aio app buildcompletes without errors
Chaining
After aio app build passes:
- Add webhook interception — invoke
commerce-app-webhooksto intercept Commerce operations - Add merchant settings — invoke
commerce-app-business-configto expose configurable settings in Commerce Admin - Extend the Admin UI — invoke
commerce-app-admin-uito add custom columns, mass actions, or menu entries in Commerce Admin - Add persistent storage — invoke
commerce-app-storageto back event handlers with queryable DB storage
References
- assets/eventing-config.ts — Reference config showing both Commerce and external event source shapes
- Accessing the Associated Commerce Instance from Runtime Actions — how
getCommerceClientresolves the Commerce base URL and auth for REST calls made from a handler - Adobe Commerce events reference — payload shapes per event, needed to determine the correct field path when extracting fields