In my ACCS vs PaaS custom modules post, I said App Builder is where most of your custom logic goes now. A few readers asked the obvious follow-up. Fine, but how do I actually build one? So this is the hands-on part. Here’s how to use App Builder for ACCS to ship a first extension, from an empty folder to a deployed action that reacts to a product save.
I’ll keep it practical. You’ll set up the Developer Console project, install the CLI, scaffold an app, wire up a Commerce event, deploy it and read the logs. If you’re new to the platform itself, start with my introduction to ACCS first.
The facts here come from Adobe’s Commerce extensibility docs and the App Builder getting started guide. The commands are taken from those docs. The action code is illustrative, though. I wrote it against the documented payload shape, so test it in your own Stage workspace before you trust it.
What you’re building with App Builder for ACCS
The goal is small on purpose. Whenever someone saves a product in the ACCS Admin, Commerce emits an event. Adobe I/O Events delivers it to a runtime action in your App Builder app. That action logs the SKU and could, for example, push it to a PIM or ERP.
It’s the out-of-process version of a catalog_product_save_after observer. The difference is that your code runs on Adobe I/O Runtime, not inside Commerce. A bug in your action won’t take down checkout. That isolation is the whole point.
1. Prerequisites
Before you touch the CLI, check these. Missing access is the most common blocker I see, and it has nothing to do with code.
- An App Builder license on the IMS org that holds your ACCS instance. Adobe notes that access comes only with a purchased license (partners can request sandbox access).
- The Developer or System Administrator role in that IMS org, plus membership in an App Builder product profile.
- Node.js. The setup guide currently says version 18 or 20 and advises against odd versions. Check the page again, because this moves.
- Developer permissions for ACCS in Commerce Cloud Manager. Without them, the ACCS API won’t show up when you add services.
- Admin access to your ACCS environment, so you can manage event subscriptions.
2. Install and log in to the aio CLI
The Adobe I/O CLI drives almost everything here. Install it globally with npm, then make sure the plugins are current.
npm install -g @adobe/aio-cli
aio -v
aio update
aio loginaio login opens a browser window for your Adobe ID. After that, the CLI caches a token, so you won’t need to log in on every command.
3. Create the Adobe Developer Console project
Next, you need a project and a workspace. In the project setup steps, Adobe uses a templated project. Here’s the short version.
- Open the Adobe Developer Console and pick the right organization.
- Choose Create project from template, then App Builder. If the option is missing, your App Builder access isn’t approved yet.
- Give it a title and app name. Keep Include Runtime with each workspace ticked.
- In the Stage workspace, add these APIs one at a time: I/O Management API, I/O Events, Adobe I/O Events for Adobe Commerce and Adobe Commerce as a Cloud Service.
- When prompted, choose OAuth Server-to-Server credentials. JWT credentials are deprecated.
- Click Download All to get the workspace JSON. You’ll use it later in the Commerce Admin.
Workspaces matter more than they look. Each one gets its own Runtime namespace, credentials and event registrations. So keep Stage for experiments and promote to Production later.
4. Scaffold the app with aio app init
Now create the code. Run aio app init in an empty folder and pick your org, project and workspace from the prompts.
mkdir product-sync && cd product-sync
aio app initThe CLI then asks which templates to install. According to the App Builder with I/O Events guide, the list includes @adobe/generator-app-events-generic and @adobe/commerce-events-ext-tpl, a template for handling Commerce events. For this walkthrough, the generic events template is enough. It adds a non-web action and an event registration in one go.
Prefer a bigger head start? Adobe also ships two starter kits. The integration starter kit covers products, customers, orders and stock sync with back-office systems. The checkout starter kit covers payment, shipping, tax and totals. Both are cloned from GitHub rather than generated by aio app init, and both are now positioned alongside Adobe’s App Management tooling.
5. Understand the project structure
Here’s roughly what you get. Exact files vary by template, so treat this tree as a guide rather than a contract.
product-sync/
├── app.config.yaml # runtime manifest + event registrations
├── .env # credentials and provider mapping (never commit)
├── .aio # local project/workspace binding
├── package.json
├── actions/
│ └── product-saved/
│ └── index.js # your event handler
├── test/
├── e2e/
└── .github/workflows/ # optional deploy pipelinesThree files deserve your attention. First, app.config.yaml declares actions, their runtime and the event registrations. Second, .env holds secrets and the provider mapping. Third, extension-point apps (like Admin UI SDK apps) put their config in an ext.config.yaml and pull it in with $include, as the configuration reference explains.
6. Connect App Builder to your ACCS instance
This step has two directions. Commerce must be able to send events to Adobe I/O. And your actions may need to call Commerce back.
Events from ACCS to Adobe I/O
In the ACCS Admin, go to Stores > Settings > Configuration > Adobe Services > Adobe I/O Events > General configuration. Paste the workspace JSON, set a unique Event Provider Instance ID and save. Then create the event provider from the same page and enable Commerce eventing.
Calls from your action back to ACCS
ACCS doesn’t use the old OAuth 1.0a integration tokens. Instead, the starter kit docs use IMS OAuth server-to-server credentials. Your .env needs values like these:
# Illustrative .env for an ACCS (SaaS) project
OAUTH_CLIENT_ID=<client id from the Developer Console>
OAUTH_CLIENT_SECRET=<client secret>
OAUTH_SCOPES=<scopes, including commerce.accs>
COMMERCE_BASE_URL=https://na1-sandbox.api.commerce.adobe.com/<tenant-id>/Note the base URL format. On ACCS it’s the REST endpoint Adobe gives you, with your tenant ID. On PaaS it’s your site URL plus /rest/. Mixing them up is an easy afternoon lost.
7. Subscribe to the product-save event
On ACCS, you can create subscriptions from the Admin or via REST. Go to System > Events > Events Subscriptions and click Add New Subscription. Pick the event, the provider and, ideally, a short list of fields.
Adobe’s subscription guide uses observer.catalog_product_save_after as its example, with entity_id, sku and is_new as fields. Keep the field list tight. Sending the full payload raises the risk of leaking sensitive data. Also tick Priority if you need speed, because non-priority events can take up to 59 seconds.
One ACCS-specific catch: SaaS doesn’t support every event. Check System > Events > Events List before you design around one.
8. Handle the event in a runtime action
Now for the actual code. The registration in app.config.yaml links Commerce events to your action. Here’s an illustrative version based on the documented format.
# Illustrative app.config.yaml (verify event code and provider metadata in your workspace)
application:
runtimeManifest:
packages:
product-sync:
license: Apache-2.0
actions:
product-saved:
function: actions/product-saved/index.js
web: 'no'
runtime: nodejs:20
inputs:
LOG_LEVEL: debug
events:
registrations:
Product saved:
description: Sync product saves from ACCS
events_of_interest:
- provider_metadata: dx_commerce_events
event_codes:
- com.adobe.commerce.observer.catalog_product_save_after
runtime_action: product-sync/product-savedAnd the handler itself. It’s deliberately boring: log, validate, return.
// Illustrative actions/product-saved/index.js (untested against a live tenant)
const { Core } = require('@adobe/aio-sdk')
async function main (params) {
const logger = Core.Logger('product-saved', { level: params.LOG_LEVEL || 'info' })
const value = params.data && params.data.value
if (!value || !value.sku) {
logger.warn('Event without a SKU, skipping')
return { statusCode: 200, body: { skipped: true } }
}
logger.info(`Product ${value.sku} saved (new: ${value.is_new})`)
// TODO: call your PIM/ERP here, or call Commerce back using IMS credentials
return { statusCode: 200, body: { sku: value.sku } }
}
exports.main = mainI’m hedging on two details. The provider metadata and event code prefix shown above match what I’ve seen in Commerce samples, but confirm them in the Developer Console. Likewise, check where the payload lands in params by logging the whole object once.
9. Run locally, deploy and read the logs
For quick iteration, aio app dev runs actions locally with hot reload and VS Code debugging. However, events from ACCS can’t reach your laptop, so you’ll still deploy to Stage to test the real flow.
aio app dev # local dev server, hot reload
aio app deploy # deploy actions and create/update event registrations
aio app logs --limit 5
aio rt activation list
aio rt activation logs <activation-id>After deploying, save a product in the Admin, wait a moment and run aio app logs. By default it fetches only the latest activation, hence the --limit flag. If nothing shows up, aio rt activation list tells you whether the action ran at all.
A small warning about aio app deploy --force-events. It deletes registrations in the workspace that aren’t in your config. That’s handy for keeping things in sync, but don’t run it against a shared workspace casually.
PaaS module pieces vs App Builder equivalents
If you think in Magento modules, this mapping helps. It isn’t one-to-one, but it’s close enough to plan with.
| PaaS module piece | App Builder equivalent on ACCS |
|---|---|
events.xml observer | I/O Events subscription + runtime action |
| Plugin that validates or blocks a flow | Commerce webhook calling a web action |
| Cron job | Scheduled trigger or alarm in Runtime (verify current support) |
system.xml + Admin page | Admin UI SDK (commerce/backend-ui/1) |
| Integration tokens (OAuth 1.0a) | IMS OAuth server-to-server credentials |
bin/magento setup:upgrade | aio app deploy |
var/log/system.log | aio app logs and activation logs |
Webhooks and the Admin UI SDK
Events are asynchronous. So what if you must block something, like rejecting a cart with a restricted SKU? That’s where Commerce webhooks come in. They call your endpoint synchronously and can throw an exception or modify the payload. On ACCS, you list available hooks under System > Webhooks > Webhooks List. Adobe’s advice is clear, though: use a webhook only when synchronous is truly needed.
For Admin screens, the Admin UI SDK lets an App Builder app add menus and pages to the Commerce Admin. It only supports the extensions configuration, using the commerce/backend-ui/1 extension point. I’ll cover it properly in a future post.
Limits and gotchas worth knowing
Runtime is serverless, so it comes with limits. These are from Adobe’s Runtime system settings page.
- Default action timeout is 60 seconds. Blocking calls, like web actions, can’t exceed one minute.
- Default memory is 256 MB, configurable from 128 MB to 4096 MB.
- Payloads and results are capped at 1 MB each.
- Action code size, including dependencies, maxes out at 22 MB.
- Activation records live for seven days, then they’re gone.
- Concurrent activations are limited to 100 per namespace.
There’s also latency. Event delivery isn’t instant, so never assume your ERP saw an order “right after” checkout.
Troubleshooting
No “Create project from template” option
Your org or role is wrong, or App Builder access isn’t provisioned. Switch org first, then ask your admin.
Action never fires
Check that eventing is enabled in the Admin and the subscription is active. Then confirm the registration exists in the Developer Console. Also, make sure the event is in the ACCS Events List.
401 or 403 when calling ACCS
Usually a missing commerce.accs scope. Adobe’s docs suggest re-downloading the workspace config and running aio app use again after adding the ACCS API.
Logs are empty
Use --limit, and remember the seven-day TTL. Also check you’re in the right workspace with aio where.
Checklist before you call it done
- ☐ App Builder license and Developer role confirmed.
- ☐ Node version matches the current setup guide.
- ☐ Stage workspace has all four APIs with OAuth server-to-server.
- ☐
.envis in.gitignoreand never shared. - ☐ Event subscription uses a minimal field list.
- ☐ Action handles missing or duplicate events safely.
- ☐ Logs checked after a real Admin save.
- ☐ Production workspace set up separately, with its own credentials.
FAQ
Do I need App Builder for every ACCS customisation?
No. Many needs are covered by configuration, API Mesh or the storefront. App Builder is for custom logic that would have lived in a PHP module.
Can I use the integration starter kit on ACCS?
Yes. Adobe says it works with PaaS or SaaS, but only one at a time. For ACCS, leave out the COMMERCE_XXXX OAuth 1.0a variables.
Is aio app dev enough for testing events?
Not on its own. It’s great for action logic, but real Commerce events need a deployed action in a workspace.
Events or webhooks for order validation?
Webhooks, if you must block or change the flow. Events, if you only need to react afterwards.
Where did my product data sync go?
That’s a different pipeline. See my post on Catalog Service and Live Search in ACCS.
My take
Honestly, the hardest part of App Builder isn’t the code. It’s the access, credentials and workspace setup. Once that’s sorted, writing a handler feels lighter than writing a module.
I do miss the instant feedback of a local observer, and the extra debugging steps take getting used to. But the trade is worth it. Your logic survives upgrades, and a bad deploy can’t break checkout. If you’re still mapping your modules, go through my PaaS to ACCS migration checklist next. And if a step here didn’t match what you saw, tell me in the comments.




