/create-custom-component
Create a custom form component for an AEM Forms Edge Delivery Services (Form block) project — scaffold blocks/form/components/<name>, extend an OOTB field type (text-input, drop-down, panel, etc.), define authoring properties in _<name>.json, wire the decorate function to the
Claude Code
$ npx -y skills add adobe/skills --skill create-custom-component --agent claude-code
Codex
$ npx skills add adobe/skills --skill create-custom-component -a codex -y
Cursor
$ npx skills add adobe/skills --skill create-custom-component -a cursor -y
Gemini CLI
$ npx skills add adobe/skills --skill create-custom-component -a gemini-cli -y
GitHub Copilot
$ npx skills add adobe/skills --skill create-custom-component -a github-copilot -y
OpenCode
$ npx skills add adobe/skills --skill create-custom-component -a opencode -y
Windsurf
$ npx skills add adobe/skills --skill create-custom-component -a windsurf -y
SKILL.md
create-custom-component.SKILL.mdname: create-custom-component
description: "Create a custom form component for an AEM Forms Edge Delivery Services (Form block) project — scaffold blocks/form/components/<name>, extend an OOTB field type (text-input, drop-down, panel, etc.), define authoring properties in _<name>.json, wire the decorate function to the form model with subscribe(), register it in mappings.js, build, and validate the output. Use when a user asks to create, scaffold, build, or fix a custom form component, form field widget, or viewType in an aem-boilerplate-forms project."
license: Apache-2.0
metadata:
version: "1.0.0"
Create Custom Form Component
Build a custom component for the AEM Forms **Form block** on Edge Delivery Services end to end — scaffold, authoring schema, `decorate` logic, model subscriptions, registration, documentation — and **prove** it works before reporting done.
When to use
- The project is a forms project derived from `aem-boilerplate-forms` (it has `blocks/form/`, `blocks/form/mappings.js`, `blocks/form/rules/index.js`).
- The user wants a new field widget/variant (slider, rating, OTP input, searchable dropdown, card choice, consent panel…) or wants to fix one under `blocks/form/components/`.
Not for: generic Edge Delivery Services blocks (use the `building-blocks` skill of the `aem-edge-delivery-services` plugin), AEM Sites HTL components (`create-component` in `aem-cloud-service`), or rule-editor logic.
References (read as needed)
| File | Read when | |------|-----------| | `references/custom-form-components.md` | Always — architecture, folder layout, JSON schema, registration | | `references/form-field-types.md` | Step 4 — the base HTML the `element` parameter already contains | | `references/authoring-field-types.md` | Step 3 — widgets for the properties dialog (`text`, `select`, `container`, …) | | `references/subscribe-api.md` | Step 4 — `subscribe()` patterns: own value, child fields, custom events | | `scripts/validate-component.mjs` | Step 7 — static validator (Node 18+, no dependencies) | | `scripts/create-e2e-test.mjs` | Step 7 — generates the component's Playwright e2e test + form JSON | | `scripts/run-e2e-test.mjs` | Step 7 — runs those tests with the project's own Playwright setup (Chromium, no retries) | | `assets/e2e/form-harness.js` | Test harness copied into the project's e2e test folder by `create-e2e-test.mjs` | | `scripts/preview-component.mjs` | Step 8: opens the component in a browser, with a live model inspector, for user feedback | | `scripts/validate-registration.js` | Optional — in-page check on an already deployed form |
Paths under `scripts/`, `assets/` and `references/` are relative to this skill's directory; resolve them to absolute paths before running commands from the repository root.
---
Workflow
Step 1: Gather requirements
Run every command in this skill from the **repository root** — the directory containing `package.json` and `blocks/form/`. Do not `cd` into a subfolder (such as `code/`) first. Then confirm — ask only for what the request does not already answer:
| Question | Why | |----------|-----| | Component name | Folder, file names, and `fd:viewType` (lowercase kebab-case, e.g. `star-rating`) | | Which OOTB field type does it extend? | Base HTML and AEM resource type | | Which authorable properties does it need? | Fields in `_<name>.json` | | What model state does it read/write, and when should `model.value` be committed? | Drives `subscribe` wiring and the README contract | | Is it a panel that watches child fields, or does it react to custom events? | Selects the subscribe pattern | | Expected behaviour to prove (what the user does, what the model should hold, how often rules fire) | Assertions of the e2e test in Step 7 |
Supported base types: `text-input`, `number-input`, `drop-down`, `button`, `checkbox`, `radio-group`, `checkbox-group`, `panel`, `date-input`, `multiline-input`, `file-input`, `email`, `telephone-input`.
Check the name is not already used: `blocks/form/components/<name>/` must not exist and `<name>` must not be in `mappings.js`.
Step 2: Scaffold
Create `blocks/form/components/<name>/{<name>.js,<name>.css,_<name>.json}` by hand: copy the base type's `blocks/form/models/form-components/_<base>.json`, set `definitions[0].id` and `models[0].id` to `<name>`, set `template["fd:viewType"]` to `<name>`, and fix `"..."` include paths so they resolve from the component folder (`../../models/form-common/...`).
Don't run the repo's `create:custom-component` script (`node tools/forms-scaffolder.js`) yourself: it takes no flags and asks its three questions at an interactive prompt, which an agent's shell can't answer. If the user prefers it, they run `npm run create:custom-component` in their own terminal. It creates the same three files and usually also registers the component (mappings, `_form.json` filter, `models/_component-definition.json`). **Do not assume it did — Step 7 verifies.**
Step 3: Define authoring properties (`_<name>.json`)
Read `references/authoring-field-types.md` to choose the widget for each property. Add custom fields to `models[0].fields`; reuse shared field groups instead of redefining them:
{ "...": "../../models/form-common/_basic-validation-fields.json#/fields" }Rules: every field has `component` and a unique `name`; `select`/`multiselect`/`radio-group`/`checkbox-group` have `options`; keep `fieldType`, `resourceType`, and `fd:viewType` from the scaffold.
Step 4: Implement `decorate`
Read `references/form-field-types.md` (base HTML) and `references/subscribe-api.md`, then pick the pattern:
| The component… | Use | |----------------|-----| | Reacts to its own value / enum / visible changes | `subscribe(fieldDiv, formId, cb, { listenChanges: true })` — handle `'register'` and `'change'` | | Watches child fields (panel) | Same on the parent; inside `'register'`, `subscribe()` each child wrapper found with `[data-id="${child.id}"]` | | Re
Read more
name: create-custom-component description: "Create a custom form component for an AEM Forms Edge Delivery Services (Form block) project — scaffold blocks/form/components/<name>, extend an OOTB field type (text-input, drop-down, panel, etc.), define authoring properties in _<name>.json, wire the decorate function to the form model with subscribe(), register it in mappings.js, build, and validate the output. Use when a user asks to create, scaffold, build, or fix a custom form component, form field widget, or viewType in an aem-boilerplate-forms project." license: Apache-2.0 metadata: version: "1.0.0"
Create Custom Form Component
Build a custom component for the AEM Forms **Form block** on Edge Delivery Services end to end — scaffold, authoring schema, `decorate` logic, model subscriptions, registration, documentation — and **prove** it works before reporting done.
When to use
- The project is a forms project derived from `aem-boilerplate-forms` (it has `blocks/form/`, `blocks/form/mappings.js`, `blocks/form/rules/index.js`).
- The user wants a new field widget/variant (slider, rating, OTP input, searchable dropdown, card choice, consent panel…) or wants to fix one under `blocks/form/components/`.
Not for: generic Edge Delivery Services blocks (use the `building-blocks` skill of the `aem-edge-delivery-services` plugin), AEM Sites HTL components (`create-component` in `aem-cloud-service`), or rule-editor logic.
References (read as needed)
| File | Read when | |------|-----------| | `references/custom-form-components.md` | Always — architecture, folder layout, JSON schema, registration | | `references/form-field-types.md` | Step 4 — the base HTML the `element` parameter already contains | | `references/authoring-field-types.md` | Step 3 — widgets for the properties dialog (`text`, `select`, `container`, …) | | `references/subscribe-api.md` | Step 4 — `subscribe()` patterns: own value, child fields, custom events | | `scripts/validate-component.mjs` | Step 7 — static validator (Node 18+, no dependencies) | | `scripts/create-e2e-test.mjs` | Step 7 — generates the component's Playwright e2e test + form JSON | | `scripts/run-e2e-test.mjs` | Step 7 — runs those tests with the project's own Playwright setup (Chromium, no retries) | | `assets/e2e/form-harness.js` | Test harness copied into the project's e2e test folder by `create-e2e-test.mjs` | | `scripts/preview-component.mjs` | Step 8: opens the component in a browser, with a live model inspector, for user feedback | | `scripts/validate-registration.js` | Optional — in-page check on an already deployed form |
Paths under `scripts/`, `assets/` and `references/` are relative to this skill's directory; resolve them to absolute paths before running commands from the repository root.
---
Workflow
Step 1: Gather requirements
Run every command in this skill from the **repository root** — the directory containing `package.json` and `blocks/form/`. Do not `cd` into a subfolder (such as `code/`) first. Then confirm — ask only for what the request does not already answer:
| Question | Why | |----------|-----| | Component name | Folder, file names, and `fd:viewType` (lowercase kebab-case, e.g. `star-rating`) | | Which OOTB field type does it extend? | Base HTML and AEM resource type | | Which authorable properties does it need? | Fields in `_<name>.json` | | What model state does it read/write, and when should `model.value` be committed? | Drives `subscribe` wiring and the README contract | | Is it a panel that watches child fields, or does it react to custom events? | Selects the subscribe pattern | | Expected behaviour to prove (what the user does, what the model should hold, how often rules fire) | Assertions of the e2e test in Step 7 |
Supported base types: `text-input`, `number-input`, `drop-down`, `button`, `checkbox`, `radio-group`, `checkbox-group`, `panel`, `date-input`, `multiline-input`, `file-input`, `email`, `telephone-input`.
Check the name is not already used: `blocks/form/components/<name>/` must not exist and `<name>` must not be in `mappings.js`.
Step 2: Scaffold
Create `blocks/form/components/<name>/{<name>.js,<name>.css,_<name>.json}` by hand: copy the base type's `blocks/form/models/form-components/_<base>.json`, set `definitions[0].id` and `models[0].id` to `<name>`, set `template["fd:viewType"]` to `<name>`, and fix `"..."` include paths so they resolve from the component folder (`../../models/form-common/...`).
Don't run the repo's `create:custom-component` script (`node tools/forms-scaffolder.js`) yourself: it takes no flags and asks its three questions at an interactive prompt, which an agent's shell can't answer. If the user prefers it, they run `npm run create:custom-component` in their own terminal. It creates the same three files and usually also registers the component (mappings, `_form.json` filter, `models/_component-definition.json`). **Do not assume it did — Step 7 verifies.**
Step 3: Define authoring properties (`_<name>.json`)
Read `references/authoring-field-types.md` to choose the widget for each property. Add custom fields to `models[0].fields`; reuse shared field groups instead of redefining them:
{ "...": "../../models/form-common/_basic-validation-fields.json#/fields" }Rules: every field has `component` and a unique `name`; `select`/`multiselect`/`radio-group`/`checkbox-group` have `options`; keep `fieldType`, `resourceType`, and `fd:viewType` from the scaffold.
Step 4: Implement `decorate`
Read `references/form-field-types.md` (base HTML) and `references/subscribe-api.md`, then pick the pattern:
| The component… | Use | |----------------|-----| | Reacts to its own value / enum / visible changes | `subscribe(fieldDiv, formId, cb, { listenChanges: true })` — handle `'register'` and `'change'` | | Watches child fields (panel) | Same on the parent; inside `'register'`, `subscribe()` each child wrapper found with `[data-id="${child.id}"]` | | Re
Skills that teach AI coding agents to work with Adobe products. A skill is a folder with a SKILL.md that an agent loads when a task calls for it (agentskills.io).
Other skills on skills.
More skills from other plugins.
karpathy-guidelines
Behavioral guidelines to reduce common LLM coding mistakes. Use when writing, reviewing, or refactoring code to avoid…
agent-builder
Design and build AI agents for any domain. Use when users: (1) ask to "create an agent", "build an assistant", or "design an AI…
Questions about create-custom-component.
- Which coding agents can use create-custom-component?
- create-custom-component installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

