From magento2-tools
Generate a Magento 2 adminhtml UI-component edit form — the modern declarative ui_component/{entity}_form.xml plus its DataProvider, button blocks, and the New/Edit/Save/Delete controllers wired to a listing. Use when the user wants to add or scaffold a new admin form, an entity edit page, a "New/Edit" backend screen, fieldsets, a WYSIWYG field, a toggle, dynamic-rows, or file/image uploaders in the Magento admin. Detects edition (Open Source vs Adobe Commerce) and flags Commerce-only form features. Produces files that pass magento2-module-review with zero Critical/High findings.
How this skill is triggered — by the user, by Claude, or both
Slash command
/magento2-tools:magento2-adminhtml-formThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Scaffold a backend entity **edit form** the modern way — a declarative
references/controllers-and-routing.mdreferences/dataprovider-patterns.mdreferences/dependent-fields.mdreferences/edition-differences.mdreferences/field-types.mdreferences/form-xml-anatomy.mdreferences/modifier-patterns.mdreferences/pitfalls.mdreferences/uploaders-wysiwyg.mdreferences/validation-rules.mdscripts/verify-form.shtemplates/acl.xmltemplates/back-button.phptemplates/controller-delete.phptemplates/controller-edit.phptemplates/controller-new.phptemplates/controller-save.phptemplates/data-provider.phptemplates/delete-button.phptemplates/di-modifier-pool.xmlScaffold a backend entity edit form the modern way — a declarative
view/adminhtml/ui_component/{entity}_form.xml bound to a DataProvider, with button
blocks and New/Edit/Save/Delete controllers — wired to an existing listing grid.
Never the legacy Magento\Backend\Block\Widget\Form.
ui_component/{entity}_form.xml) + a
PHP DataProvider. Never generate Block\Widget\Form / Block\Widget\Form\Container.<namespace> and file name → {entity}_formprovider in js_config → {entity}_form.{entity}_form_data_source<dataSource name> and <dataProvider name> → {entity}_form_data_source<uiComponent name> → {entity}_form<route_id>_<controller>_edit.xml (route id + controller path + action)<buttons> in the form reference PHP block classes; the
page errors if they are missing. Always emit GenericButton + Back, Save, Delete
(and SaveAndContinue when save-and-continue is requested).Magento\Ui\DataProvider\AbstractDataProvider
(modifier-less default) or Magento\Ui\DataProvider\ModifierPoolDataProvider (only when
modifiers are requested). getData() returns [ $id => [field => value, …] ] — flat fields,
keyed by entity id. Inject DataPersistorInterface so a failed save (and the New screen)
repopulates. See references/dataprovider-patterns.md.$this->getRequest()->getPostValue() → [{entity}_id, …fields]. Do not unwrap a
data/general key. Normalise empty id to null before repository->save().Modifier + di-modifier-pool.xml only when extending an existing form
(product/customer) or building fields dynamically. See references/modifier-patterns.md.translate attribute. acl.xsd allows only id, title, sortOrder,
disabled on <resource>. menu.xml does allow translate. Mixing them fails xmllint.<referenceContainer name="content"><uiComponent .../></referenceContainer>. Do not add <update handle="styles"/> (storefront-only).{ctx.edition}. Content-staging tabs and B2B company forms are
Adobe Commerce only — never emit staging wiring on Open Source. See
references/edition-differences.md.magento2-context/references/tdd-discipline.md.--standard=Magento2 PHPCS is the gate. See
magento2-context/references/php-coding-style.md.app/code/vendor/*/Magento core to infer conventions, entity shapes,
naming, or wiring. Narrow exceptions: the target module/class of this operation, and the specific
contract of a module this code explicitly depends on. Affirm sources in the final report. See
magento2-context/references/source-of-truth.md.Invoke magento2-context. Capture {ctx}. Abort with a clear message if the target module
does not exist (offer magento2-module-create) or if {ctx.magento_root} is unresolved.
Ask for any missing values:
| Input | Default | Notes |
|---|---|---|
| Target module | (ask) | Existing {Vendor}_{Module}; offer magento2-module-create if absent |
| Entity name | (ask) | PascalCase {Entity} + lowercase {entity} (e.g. Faq/faq) |
| Data loading | (ask) | Existing repository/collection for the entity (must exist) |
| Primary field | {entity}_id | The id column / request param / redirect key |
| Fields | (ask) | Each: code, label, formElement, dataType, required?, source options |
| Surfaces | (ask) | modifier? wysiwyg? toggle? dynamicRows? uploader? dependent fields? delete? save-and-continue? menu entry? |
| Listing to return to | (ask) | Name of the grid the Back button / save redirect target (or "none yet") |
| ACL resource | {Vendor}_{Module}::{entity} | Guards Save/Delete controllers |
Consult references/field-types.md, references/validation-rules.md,
references/edition-differences.md while asking so offered options match {ctx.edition}.
Present the file plan (every path to create/modify, surfaces enabled, ACL/route/menu touches). Wait for "proceed."
3A — Write the failing test (RED). Before any form code, add a test under Test/Integration/
that boots the DataProvider and asserts getData() returns the entity shaped as
[$id => [...]], plus a controller test (Save redirects + sets a success message; Edit 404s/
redirects for a missing id, renders for an existing one). If no test DB is available, fall back to
a unit test of any Modifier::modifyMeta/modifyData and record the gap in the Phase 5
report. Run it and confirm it fails for the right reason. See
magento2-context/references/tdd-discipline.md and
magento2-test-generate/references/integration-patterns.md.
3B — Generate (GREEN). Write the minimal files from templates/ to turn 3A green:
templates/form.xml → view/adminhtml/ui_component/{entity}_form.xmltemplates/data-provider.php → Model/{Entity}/DataProvider.phptemplates/controller-new.php, controller-edit.php, controller-save.php, controller-delete.phptemplates/generic-button.php + back-button.php, save-button.php, delete-button.php
(+ save-and-continue-button.php when requested)templates/routes.xml, acl.xml, menu.xml (as needed), layout-edit.xmltemplates/modifier.php + di-modifier-pool.xml (modifier surface)xmllint --noout on every XML ({ctx.tools.xmllint}): form, layout, routes, acl, menu, di.php -l on every PHP file.{ctx.runner} vendor/bin/phpunit; confirm it now passes; run
the module suite to confirm no regressions.${CLAUDE_PLUGIN_ROOT}/skills/magento2-context/scripts/add-license-headers.sh {ctx.magento_root}/app/code/{Vendor}/{Module} {Vendor}
to stamp the standard copyright header onto every new .php (idempotent — it skips files that already
carry it). If you add a composer.json require entry, resolve a bounded constraint via
${CLAUDE_PLUGIN_ROOT}/skills/magento2-context/scripts/resolve-dep-constraint.sh <vendor/package> —
never "*". See magento2-context/references/module-hygiene.md.magento2-module-review on the module → zero Critical/High.scripts/verify-form.sh runs the XML + PHP lints in one pass.Brief Markdown saved to {output_root}/adminhtml-forms/{Vendor}_{Module}-{entity}-form-{date}.md:
bin/magento setup:upgrade (if di/routes/menu/acl changed), cache flush,
setup:di:compile note for productionmagento2-module-review and magento2-test-generateDocs may now be stale. This change modified module code. Run
magento2-docs-generate --module={Vendor}_{Module}to refresh the module's README, CHANGELOG, anddocs/*.md(technical reference, guides, and API references as applicable).
/magento2-adminhtml-form --module=Acme_Faq --entity=Faq --primary=faq_id --wysiwyg=content --listing=acme_faq_listing [--docs-root=<path>]
--docs-root=<path> — output-root override; see "Output root" below.
{ctx.magento_root}/app/code/{Vendor}/{Module}/view/adminhtml/ui_component/{entity}_form.xml
{ctx.magento_root}/app/code/{Vendor}/{Module}/Model/{Entity}/DataProvider.php
{ctx.magento_root}/app/code/{Vendor}/{Module}/Controller/Adminhtml/{Entity}/{New,Edit,Save,Delete}.php
{ctx.magento_root}/app/code/{Vendor}/{Module}/Block/Adminhtml/{Entity}/Edit/*Button.php
{ctx.magento_root}/app/code/{Vendor}/{Module}/view/adminhtml/layout/{vendor_lower}_{entity}_{entity}_edit.xml
{ctx.magento_root}/app/code/{Vendor}/{Module}/etc/adminhtml/{routes,menu,di}.xml
{ctx.magento_root}/app/code/{Vendor}/{Module}/etc/acl.xml
{ctx.magento_root}/app/code/{Vendor}/{Module}/Test/Integration/.../{Entity}FormTest.php # test-first (Phase 3A)
{output_root}/adminhtml-forms/{Vendor}_{Module}-{entity}-form-{date}.md
{output_root} defaults to .docs ({ctx.docs_root}), anchored at the project root, never
under {ctx.magento_root}. See the Artifact location rule in magento2-context/SKILL.md.
--docs-root)This skill accepts --docs-root=<path> (see
magento2-context/references/artifact-layout.md). When set, write the run report (and any
report artifacts) under <path>/adminhtml-forms/; otherwise default to
{ctx.docs_root}/adminhtml-forms/. magento2-feature-implement passes this so a feature
run's reports collect under its folder.
references/form-xml-anatomy.md — {entity}_form.xml structure + the naming contract.references/dataprovider-patterns.md — DataProvider base classes, getData() shape, persistor.references/modifier-patterns.md — ModifierInterface + Pool (optional surface).references/field-types.md — formElement/dataType map; toggle, dynamicRows.references/validation-rules.md — required-entry, validate-number, custom + server-side.references/dependent-fields.md — show/hide via imports/exports, switcherConfig.references/controllers-and-routing.md — controllers, ACL, routes, layout handle derivation.references/uploaders-wysiwyg.md — image/file uploader controller + WYSIWYG wiring.references/edition-differences.md — Open Source vs Adobe Commerce (staging, B2B).references/pitfalls.md — blank-form / silent-save / 404 root causes and fixes.magento2-context/references/tdd-discipline.md — shared test-first loop (Phase 3A).magento2-context/references/source-of-truth.md — source-of-truth hierarchy + the
no-unrelated-module-scanning rule (allowed reads, live-doc fetch protocol, report affirmation).templates/form.xmltemplates/data-provider.phptemplates/controller-new.php, controller-edit.php, controller-save.php, controller-delete.phptemplates/generic-button.php, back-button.php, save-button.php, delete-button.php,
save-and-continue-button.phptemplates/routes.xml, acl.xml, menu.xml, layout-edit.xmltemplates/modifier.php, di-modifier-pool.xml (modifier surface)All templates follow the placeholder registry in
magento2-context/references/placeholder-schema.md using the {Vendor} / {Module} /
{Entity} / {entity} convention. Every token used must be registered there —
tests/test-placeholder-tokens.sh enforces it.
<button> referenced by the form.[$id => [...]] and injects DataPersistorInterface.acl.xml has no translate attribute; every XML passes xmllint --noout.getData() shape and the Save/Edit controller behaviour was written and
watched to fail before the form existed, and passes after (or the gap is recorded).magento2-module-review with zero Critical/High findings.| Pitfall | How the skill avoids it |
|---|---|
| Blank form on Edit | The five-name naming contract is generated consistently |
| Save writes empty/garbage rows | Flat getPostValue(); empty id normalised to null |
| Form errors on load | Button block classes always generated for referenced buttons |
| New-record / failed-save loses input | DataPersistorInterface injected into the DataProvider |
xmllint fails on acl.xml | No translate attribute on <resource> |
| Blank content area | Layout handle derived as <route_id>_<controller>_edit |
| WYSIWYG renders as plain textarea | Canonical formElement="wysiwyg" + wysiwygConfigData |
| Commerce-only features on Open Source | Edition checked in Phase 1; staging/B2B gated |
Legacy Block\Widget\Form | Skill refuses; declarative UI component only |
| Phase | Skill |
|---|---|
| 0 | magento2-context |
| upstream | magento2-module-create — create the module first if absent |
| sibling | magento2-eav-attribute — add an attribute, then expose it on this form |
| downstream | magento2-test-generate — fuller unit/integration/MFTF coverage |
| gate | magento2-module-review — acceptance gate (zero Critical/High) |
| (caller) | magento2-feature-implement — the admin-form slice of a feature |
npx claudepluginhub muon-m2/magento2-toolsGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.
2plugins reuse this skill
First indexed Jul 9, 2026