Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
adobe avatar

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)
At a glance

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
From the docs

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
SKILL.md
Default routes are **`POST /submit`** and **`GET /status`**; omit **`routes`** unless paths differ from defaults.
SKILL.md
The **web** action in **`*.config.yaml`** / **`ext.config.yaml`** must use **`web: "raw"`** and **`require-adobe-auth: false`**
SKILL.md
npx skills add https://github.com/adobe/ffcpe-custom-node-sdk --skill ffcpe-app-builder-actions

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs59
Last updatedJune 1, 2026
Repositoryadobe/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

SKILL.mdMarkdownGitHub ↗

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):

RuleDetail
Alphanumeric onlyHyphens and special characters are rejected (demo-qr-custom-ffcpe-nodesProject name … is invalid). Use camelCase or a short slug: demoQrFfcpe.
Max 20 charactersLonger names fail with Project name length must be less than 20.
Name ≠ OpenWhisk packageThe 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-createdProduction 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:

CommandKey flags
aio console workspace create--projectName <name> (required), --name <name> (required)
aio console workspace selectpositional [WORKSPACEIDORNAME] + --projectId <id>
aio console workspace list--projectId <id> (not --projectName)
aio console workspace downloadpositional [DESTINATION] + --projectId <id>

2. Initialise the project

aio app init -y --no-login --standalone-app --no-install

3. 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-input

If `.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' >> .gitignore

Also 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 e2e

5. 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: debug

6. 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-loader

7. 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@latest

Use 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.json

The `.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-loader

If 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 deploy

Deploy output lists web action base URLs, e.g.:

    https://3326322-myproject-stage.adobeioruntime.net/api/v1/web/my-ffcpe-app/my-action-web

2. Derive catalog endpoints from that base URL (default Hono routes from `mountFfcpeNodeRoutes`):

FieldPattern
`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 select

4. 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.json

If 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 → removed web-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-src line; FFCPE web + worker pairs declared (one pair per custom action).
  • `webpack-config.js`: libraryTarget: "commonjs2", esbuild-loader, .ts/.js extensions — replace the init-bare generated 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.

Backend & APIsbackendintegrations

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.