
Ffcpe App Builder Actions
- 59 installs
- Updated June 1, 2026
- adobe/ffcpe-custom-node-sdk
ffcpe-app-builder-actions is a Claude Code skill that implements FFCPE custom-action web and worker OpenWhisk actions on Adobe App Builder using the ffcpe-custom-node core and app-builder packages.
About
This skill guides developers implementing FFCPE custom-action web and worker OpenWhisk actions in an Adobe App Builder project using @adobe/ffcpe-custom-node-core and @adobe/ffcpe-custom-node-app-builder. It covers wiring Hono routes with mountFfcpeNodeRoutes, writing a worker handler with createFfcpeNodeWorker, the async submit/status polling contract, and the ext.config.yaml runtime manifest. A developer uses it when scaffolding or migrating FFCPE action pairs so Adobe Workflow Builder can invoke them as custom actions.
- Scaffolds FFCPE web + worker OpenWhisk action pairs on Adobe App Builder
- Covers mountFfcpeNodeRoutes / createFfcpeNodeWorker and the submit/status HTTP contract
- Documents ext.config.yaml (web: raw, require-adobe-auth: false) and webpack/esbuild-loader setup
Ffcpe App Builder Actions by the numbers
- 59 all-time installs (skills.sh)
- Ranked #3,167 of 4,347 Backend & APIs skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
ffcpe-app-builder-actions capabilities & compatibility
Free skill; requires an Adobe App Builder / Adobe I/O account to deploy the actions
- Capabilities
- api development · serverless scaffolding · openwhisk actions
- Use cases
- api development · devops
- Pricing
- Free
What ffcpe-app-builder-actions says it does
Use this skill when implementing or extending **Workflow Builder custom actions** in an **Adobe App Builder** (OpenWhisk) repo
Default routes are **`POST /submit`** and **`GET /status`**; omit **`routes`** unless paths differ from defaults.
The **web** action in **`*.config.yaml`** / **`ext.config.yaml`** must use **`web: "raw"`** and **`require-adobe-auth: false`**
npx skills add https://github.com/adobe/ffcpe-custom-node-sdk --skill ffcpe-app-builder-actionsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 59 |
|---|---|
| Last updated | June 1, 2026 |
| Repository | adobe/ffcpe-custom-node-sdk ↗ |
What it does
Scaffold or migrate FFCPE web/worker OpenWhisk action pairs in an Adobe App Builder repo so Workflow Builder can call them as custom actions.
Who is it for?
Teams shipping an App Builder application whose actions Adobe Workflow Builder invokes as async custom-actions with submit plus status polling.
Skip if: Contributing to the SDK source monorepo, or authoring catalog-entry.json and running aio ffcpe catalog (companion skills cover those).
When should I use this skill?
Scaffolding or migrating FFCPE web/worker pairs, fixing ext.config.yaml / runtimeManifest, or debugging hono-openwhisk-adapter web actions.
What you get
A working web action mounted with mountFfcpeNodeRoutes plus a worker built with createFfcpeNodeWorker, deployable and registrable as an FFCPE custom-action.
- Hono web action exporting main via ToOpenWhiskAction
- Worker action via createFfcpeNodeWorker
- Updated ext.config.yaml runtimeManifest
By the numbers
- Default routes are POST /submit and GET /status
- Console project names are alphanumeric only and max 20 characters
Files
FFCPE App Builder actions (@adobe/ffcpe-custom-node-core, @adobe/ffcpe-custom-node-app-builder)
Use this skill when implementing or extending Workflow Builder custom actions in an Adobe App Builder (OpenWhisk) repo with `@adobe/ffcpe-custom-node-core` and `@adobe/ffcpe-custom-node-app-builder`.
For HTTP field-level contract details, see `docs/custom-action-requests.md` in this SDK repo. For consumer install and examples, see `README.md`. For package install, local linking, and security guidance, see `ffcpe-custom-node-sdk`. For `catalog-entry.json` and `aio ffcpe catalog`, use `ffcpe-catalog-entry-json` and `aio-ffcpe-cli` from adobe/aio-cli-plugin-ffcpe:
npx skills add adobe/aio-cli-plugin-ffcpe --all -y---
Packages and peers
- `@adobe/ffcpe-custom-node-core` — contract types, `handleFfcpeSubmit` / `handleFfcpeStatus`, `runWorkerJob`, input/output helpers, IMS inbound auth helpers.
- `@adobe/ffcpe-custom-node-app-builder` — `mountFfcpeNodeRoutes`, `createFfcpeNodeWorker`, AIO `JobStore`, OpenWhisk orchestrator, `buildStatusUrl`, `createAioLogger`.
Declare `hono` as a peer (your app chooses the version). Typical App Builder peers also include `@adobe/aio-lib-state`, `@adobe/aio-lib-ims`, `@adobe/aio-lib-core-logging`, and `openwhisk` — align versions with your template’s `package.json`.
---
Web action (Hono)
1. Import `Hono`, `ToOpenWhiskAction` from `hono-openwhisk-adapter`, and `mountFfcpeNodeRoutes` from `@adobe/ffcpe-custom-node-app-builder`. 2. `const app = new Hono()`, then `mountFfcpeNodeRoutes(app, { worker: { package, name }, web: { package, name } })`. `web` and `worker` must match OpenWhisk package and action names in your manifest (for `statusUrl` and async invoke). 3. Default routes are `POST /submit` and `GET /status`; omit `routes` unless paths differ from defaults. 4. `export const main = ToOpenWhiskAction(app)` (or the entry shape your bundler expects).
The web action in *`.config.yaml** / **ext.config.yaml** must use **web: "raw"** and **require-adobe-auth: false**, plus a supported Node runtime (e.g. **nodejs:22`**):
runtimeManifest:
packages:
my-ffcpe-app:
actions:
my-web-action:
function: actions/my-web-action.ts
web: "raw"
runtime: nodejs:22
annotations:
require-adobe-auth: false
my-worker:
function: actions/my-worker.ts
runtime: nodejs:22---
Worker action
1. Implement `FfcpeNodeWorkerHandler`: `ctx` has `jobId`, `inputs` (`FfcpeInput[]`), `params` (submit `parameters`), `logger`, optional `authContext`, `args` (full `main(args)`). 2. Return `{ status: "completed", outputs?: Output[] }` or `{ status: "failed", error: string }`. Use `createTextOutput`, `createImageOutput`, `createVideoOutput`, `createJsonOutput`. 3. `export const main = createFfcpeNodeWorker(handler, options?)`. `requiredInputNames` refers to each input’s `port`, not the display `name`.
Use `getTextInput`, `getImageInput`, etc. with `ctx.inputs` (helpers take the inputs array, not the full submit body).
Manifest `inputs` (secrets): usually environment variables — use `process.env`, not `ctx.inputs`. Do not log secrets.
---
Starting from scratch with init-bare
If you have no App Builder project yet, the fastest path is aio app init --standalone-app (or the init-bare script wrapper). Warning: this generator produces more than a bare skeleton — it scaffolds web-src/, actions/generic/, actions/publish-events/, test/, and e2e/ that are irrelevant to a headless FFCPE action. Follow these steps in order:
1. Create the Console project and workspace
# Create the project
aio console project create -n myproject -t "My Project Title" --json
# Stage workspace is created automatically; select it (use the project ID from the create output)
aio console project select myproject
aio console workspace select Stage --projectId <PROJECT_ID>Console project name constraints (from real setup failures):
| Rule | Detail |
|---|---|
| Alphanumeric only | Hyphens and special characters are rejected (demo-qr-custom-ffcpe-nodes → Project name … is invalid). Use camelCase or a short slug: demoQrFfcpe. |
| Max 20 characters | Longer names fail with Project name length must be less than 20. |
| Name ≠ OpenWhisk package | The Console project name (e.g. demoQrFfcpe) can differ from the `runtimeManifest` package name in app.config.yaml (e.g. demo-qr-custom-ffcpe-nodes). Hyphens are fine in the YAML package and action names. |
| Stage auto-created | Production and Stage workspaces are created with the project; you usually do not need aio console workspace create for a first deploy. |
Console CLI flag reference — flags differ by command; mismatches throw NonExistentFlagsError:
| Command | Key flags |
|---|---|
aio console workspace create | --projectName <name> (required), --name <name> (required) |
aio console workspace select | positional [WORKSPACEIDORNAME] + --projectId <id> |
aio console workspace list | --projectId <id> (not --projectName) |
aio console workspace download | positional [DESTINATION] + --projectId <id> |
2. Initialise the project
aio app init -y --no-login --standalone-app --no-install3. Wire the local app to the Console workspace
`aio app use --no-input` alone fails after init-bare because the local .aio file has no org/project/workspace context yet. The correct sequence is:
# Download the workspace credentials/config from Console
aio console workspace download # saves e.g. <orgId>-<project>-Stage.json
# Import that config — this populates .aio and .env
aio app use <orgId>-<project>-Stage.json --overwrite --no-inputIf `.env` already exists (common after aio app init), a bare aio app use <file>.json prompts interactively and can hang in non-interactive/agent sessions. Always pass `--overwrite --no-input` when importing a downloaded workspace config into a scaffolded project.
The downloaded JSON file contains secrets (client credentials, API keys). Add it to .gitignore immediately after downloading — before any git add. The generated .gitignore does not cover this file automatically:
# Add the specific file, or use the pattern [0-9]*-*-*.json to cover all workspaces
echo '<orgId>-<project>-Stage.json' >> .gitignoreAlso confirm .env is ignored (the generated .gitignore covers this via .env*, but verify it is present).
4. Clean up the scaffolded files
The generator creates files that conflict with a headless FFCPE action. Remove them:
rm -rf web-src actions/generic actions/publish-events test e2e5. Fix app.config.yaml
The generated YAML includes web: web-src at the application level, which causes the frontend build step to run (and fail) on a headless project. Remove that line and replace the scaffolded actions with your FFCPE pair:
application:
actions: actions
runtimeManifest:
packages:
my-ffcpe-app:
license: Apache-2.0
actions:
my-action-web:
function: actions/my-action/my-action.web.ts
web: "raw"
runtime: nodejs:22
annotations:
require-adobe-auth: false
my-action-worker:
function: actions/my-action/my-action.worker.ts
runtime: nodejs:22
inputs:
LOG_LEVEL: debug6. Replace webpack-config.js
The generator emits a config that uses ts-loader and is missing libraryTarget: "commonjs2". Replace it entirely (not a merge — the generated output section is wrong):
// webpack-config.js
module.exports = {
output: {
libraryTarget: 'commonjs2',
},
module: {
rules: [
{
test: /\.[jt]sx?$/,
loader: 'esbuild-loader',
options: { target: 'es2020' },
},
],
},
resolve: {
extensions: ['.ts', '.js'],
},
}Install the loader:
npm install -D esbuild-loader
# or: pnpm add -D esbuild-loader7. Install FFCPE packages
npm view @adobe/ffcpe-custom-node-core version
npm view @adobe/ffcpe-custom-node-app-builder version
npm view hono-openwhisk-adapter version
npm install @adobe/ffcpe-custom-node-core@latest @adobe/ffcpe-custom-node-app-builder@latest hono hono-openwhisk-adapter@latestUse the latest published versions — run `npm view <package> version` before install (or install with `@latest` as above). Do not assume or pin stale ranges such as `0.1.x`; those may not exist on the registry. Omitting a version also resolves to latest, but `@latest` makes intent explicit in docs and scripts. After install, `package-lock.json` pins exact versions for reproducibility.
Image outputs: createImageOutput expects an HTTPS URL, not raw bytes. Upload with `@adobe/aio-lib-files` (init(), write(), generatePresignURL()) — see the thumbnail example in the SDK `README.md`.
---
Multiple custom actions in one app
Each FFCPE node needs its own web + worker pair and co-located `<action-name>.entry.json`. Declare all pairs under one `runtimeManifest` package (or split across packages if your org prefers). Example with two nodes:
runtimeManifest:
packages:
my-ffcpe-app:
actions:
generate-foo-web: { function: actions/generate-foo/generate-foo.web.ts, web: "raw", … }
generate-foo-worker: { function: actions/generate-foo/generate-foo.worker.ts, … }
decode-foo-web: { function: actions/decode-foo/decode-foo.web.ts, web: "raw", … }
decode-foo-worker: { function: actions/decode-foo/decode-foo.worker.ts, … }Each `.web.ts` calls `mountFfcpeNodeRoutes` with its own `worker.name` / `web.name` matching the YAML action keys.
---
Adding actions to an existing App Builder repo
1. Co-located files (recommended)
For each custom action, keep web, worker, and catalog entry JSON in the same directory, sharing the same `<action-name>` prefix:
- `<action-name>.web.ts` or `.js` — Hono submit/status web action
- `<action-name>.worker.ts` or `.js` — async worker
- `<action-name>.entry.json` — run-workflow catalog entry (same document you pass to `aio ffcpe catalog validate` / `register`)
Example layout:
actions/
resize-image/
resize-image.web.ts
resize-image.worker.ts
resize-image.entry.jsonThe `.entry.json` file is not a separate template artifact — it is the catalog registration payload (`handlerType: "custom-action"`, ports, `customActionConfig`, discovery fields). Author it with skill `ffcpe-catalog-entry-json`.
Flat co-location (same folder, no subdir) is fine if your repo already uses `actions/resize-image.web.ts` next to `actions/resize-image.worker.ts` — still name the catalog file `resize-image.entry.json` beside them.
2. *`.config.yaml`**
Update `runtimeManifest` (or equivalent): declare the web action with `web: "raw"` and `annotations.require-adobe-auth: false`, plus the worker action; `function` paths must match your build outputs. `mountFfcpeNodeRoutes` `package` / `name` must match this YAML.
3. TypeScript + webpack (CJS for dist)
Install:
npm install -D esbuild-loader
# or
pnpm add -D esbuild-loaderIf the project was initialized with init-bare, replace `webpack-config.js` entirely — the generated file uses ts-loader and lacks libraryTarget: "commonjs2". For other existing projects, merge into *`webpack-config.js** (preserve existing rules via **webpack-merge** or careful **Object.assign`**):
output: {
libraryTarget: "commonjs2", // Important for CommonJS output
},
module: {
rules: [
{
// Match `.js`, `.jsx`, `.ts` or `.tsx` files
test: /\.[jt]sx?$/,
loader: "esbuild-loader",
options: {
// JavaScript version to compile to
target: "es2020",
},
},
],
},
resolve: {
extensions: [".ts", ".js"], // Add .ts and .js to the list of extensions to resolve
},Deployed Runtime bundles are CommonJS; source can still be ESM-style TypeScript.
---
Register in the catalog (after deploy)
1. Deploy web + worker actions:
aio app build
aio app deployDeploy output lists web action base URLs, e.g.:
https://3326322-myproject-stage.adobeioruntime.net/api/v1/web/my-ffcpe-app/my-action-web2. Derive catalog endpoints from that base URL (default Hono routes from `mountFfcpeNodeRoutes`):
| Field | Pattern |
|---|---|
| `submitEndpoint` | {web-action-base-url}/submit |
| `statusEndpoint` | {web-action-base-url}/status |
Use `/api/v1/web/` (not /apis/v1/). The Runtime namespace in the hostname is lowercase (e.g. 3326322-demoqrffcpe-stage even when the Console project is demoQrFfcpe).
3. Fill in `<action-name>.entry.json` — `handlerType: "custom-action"`, port names matching the worker, endpoints from step 2, and `customActionConfig.authentication`. When the web action uses default `mountFfcpeNodeRoutes` auth (no `authenticate: null`), set `"authentication": { "type": "ims_service_token" }` — not `none`. See `ffcpe-catalog-entry-json`.
4. Install and auth the CLI plugin:
npm install -g @adobe/aio-cli
aio plugins:install @adobe/aio-cli-plugin-ffcpe
aio login
aio console org select4. Validate and register — do not use raw curl (path is the co-located `.entry.json`):
aio console org select # required before catalog API calls
aio ffcpe catalog validate --file ./actions/resize-image/resize-image.entry.json
aio ffcpe catalog register --file ./actions/resize-image/resize-image.entry.jsonIf the action type already exists, use `aio ffcpe catalog update <actionType> --file …` instead of `register`.
---
Agent checklist
- New project: ran
init-bare→ downloaded workspace config →aio app use <file>.json --overwrite --no-input→ removedweb-src/,actions/generic/,actions/publish-events/,test/,e2e/. - Console project name: alphanumeric, ≤20 chars; may differ from `runtimeManifest` package name.
- `app.config.yaml`: no
web: web-srcline; FFCPE web + worker pairs declared (one pair per custom action). - `webpack-config.js`:
libraryTarget: "commonjs2",esbuild-loader,.ts/.jsextensions — replace theinit-baregenerated file, don't merge. - Web + worker names align with `mountFfcpeNodeRoutes` and YAML.
- Web: `web: "raw"`, `annotations.require-adobe-auth: false`.
- Worker: FFCPE data from `ctx.inputs`; secrets from `process.env` only; image outputs via `aio-lib-files` presigned URLs.
- Catalog: `<action-name>.entry.json` co-located; port names match worker ports; `submitEndpoint` / `statusEndpoint` copied from deploy output +
/submit//status; auth `ims_service_token` when using default route auth; `aio ffcpe catalog validate` then `register` (or `update`).
Related skills
FAQ
What must web and worker names match in mountFfcpeNodeRoutes?
web and worker must match the OpenWhisk package and action names in your manifest for statusUrl and async invoke to work.
How must the web action be configured in the manifest?
The web action must use web: "raw" and require-adobe-auth: false, plus a supported Node runtime such as nodejs:22.