admin-form
Skill6 starsChanged 28 days agoMITView on GitHub
Generate a Magento 2 adminhtml UI-component edit form: `ui_component/{entity}_form.xml` plus DataProvider, buttons, and New/Edit/Save/Delete controllers wired to a listing. Use to add or scaffold an admin form, entity edit page, backend screen, fieldsets, WYSIWYG field, toggle, dynamic-rows, or file/image uploaders in the Magento admin. Detects edition and flags Commerce-only features. For the grid use magento2-tools:admin-listing.
We split this skill into the rules it gives an agent
12 rules. About 0 are common, 4 are distinctive, and 8 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈4
Distinctive · estimate
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 17
Never the legacy Magento\Backend\Block\Widget\Form.
- Very specific: line 41
The modifier/Pool is an OPTIONAL surface: Default to modifier-less for a new simple form.
- Very specific: line 50
Test-first: Write the failing test before the form code (red → green) and watch it fail for the right reason.
- Distinctive: line 71
Ask for any missing values:
- Very specific: line 94
3A — Write the failing test (RED): Before any form code, add a test under Test/Integration/ that boots the DataProvider and asserts …
- Very specific: line 103
3B — Generate (GREEN): Write the minimal files from templates/ to turn 3A green:
- Very specific: line 117
Run the Phase 3A test with {ctx.runner} vendor/bin/phpunit.
- Very specific: line 120
Apply the shared module-hygiene baseline (required): After generating or modifying PHP files, run …
- Distinctive: line 134
Test path + red→green evidence (or recorded gap)
- Very specific: line 205
All templates follow the placeholder registry in context/references/placeholder-schema.md using the {Vendor} / {Module} / {Entity} / …
- Distinctive: line 217
watched to fail: before the form existed, and passes after (or the gap is recorded).
- Distinctive: line 218
No Commerce-only wiring emitted on Open Source.
≈ “Common” is an estimate: at least 1 in 5 of the 308,232 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Magento 2 Adminhtml Form
Scaffold 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.
Core Rules
- Declarative UI component only. The form is XML (
ui_component/{entity}_form.xml) + a PHPDataProvider. Never generateBlock\Widget\Form/Block\Widget\Form\Container. - The naming contract prevents the blank form. These five names MUST agree, or the form
renders empty with no error:
- form
<namespace>and file name →{entity}_form providerinjs_config→{entity}_form.{entity}_form_data_source<dataSource name>and<dataProvider name>→{entity}_form_data_source- layout
<uiComponent name>→{entity}_form - layout handle file →
<route_id>_<controller>_edit.xml(route id + controller path + action)
- form
- Button blocks are required. The
<buttons>in the form reference PHP block classes; the page errors if they are missing. Always emitGenericButton+Back,Save,Delete(andSaveAndContinuewhen save-and-continue is requested). - DataProvider shape is exact. Extend
Magento\Ui\DataProvider\AbstractDataProvider(modifier-less default) orMagento\Ui\DataProvider\ModifierPoolDataProvider(only when modifiers are requested).getData()returns[ $id => [field => value, …] ]— flat fields, keyed by entity id. InjectDataPersistorInterfaceso a failed save (and the New screen) repopulates. Seereferences/dataprovider-patterns.md. - Save reads FLAT post data. A standard UI form posts fields flat;
$this->getRequest()->getPostValue()→[{entity}_id, …fields]. Do not unwrap adata/generalkey. Normalise empty id tonullbeforerepository->save(). - The modifier/Pool is an OPTIONAL surface. Default to modifier-less for a new simple form.
Generate a
Modifier+di-modifier-pool.xmlonly when extending an existing form (product/customer) or building fields dynamically. Seereferences/modifier-patterns.md. - acl.xml has no
translateattribute.acl.xsdallows onlyid,title,sortOrder,disabledon<resource>.menu.xmldoes allowtranslate. Mixing them fails xmllint. - No storefront handles. The edit layout is just
<referenceContainer name="content"><uiComponent .../></referenceContainer>. Do not add<update handle="styles"/>(storefront-only). - Edition awareness. Detect
{ctx.edition}. Content-staging tabs and B2B company forms are Adobe Commerce only — never emit staging wiring on Open Source. Seereferences/edition-differences.md. - Test-first. Write the failing test before the form code (red → green) and watch it fail
for the right reason. See
context/references/tdd-discipline.md. - Coding style. Generated PHP follows PER-CS 3.0 baseline, Magento 2 standard taking
precedence;
--standard=Magento2PHPCS is the gate. Seecontext/references/php-coding-style.md. - Source of truth. Generate from templates → shared references → baked-in Magento 2 knowledge
→ official Magento/Adobe docs (live-fetched only when uncertain). Do NOT read, grep, or "study"
other modules under
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. Seecontext/references/source-of-truth.md.
Workflow
Phase 0 — Context Resolution
Invoke context. Capture {ctx}. Abort with a clear message if the target module
does not exist (offer module-create) or if {ctx.magento_root} is unresolved.
Phase 1 — Resolve Inputs
Ask for any missing values:
| Input | Default | Notes |
| Target module | (ask) | Existing {Vendor}_{Module}; offer 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}.
Phase 2 — Plan
Present the file plan (every path to create/modify, surfaces enabled, ACL/route/menu touches). Wait for "proceed."
Phase 3 — Test First, then Generate
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
context/references/tdd-discipline.md and
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.phpwhen requested)templates/routes.xml,acl.xml,menu.xml(as needed),layout-edit.xml- Optional:
templates/modifier.php+di-modifier-pool.xml(modifier surface)
Phase 4 — Verify
xmllint --noouton every XML ({ctx.tools.xmllint}): form, layout, routes, acl, menu, di.php -lon every PHP file.- Run the Phase 3A test with
{ctx.runner} vendor/bin/phpunit; confirm it now passes; run the module suite to confirm no regressions. - Confirm the five naming-contract names agree (the blank-form check).
- Apply the shared module-hygiene baseline (required). After generating or modifying PHP files, run
${CLAUDE_PLUGIN_ROOT}/skills/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 acomposer.jsonrequireentry, resolve a bounded constraint via${CLAUDE_PLUGIN_ROOT}/skills/context/scripts/resolve-dep-constraint.sh <vendor/package>— never"*". Seecontext/references/module-hygiene.md. - Optionally run
reviewon the module → zero Critical/High. scripts/verify-form.shruns the XML + PHP lints in one pass.
Phase 5 — Report
Brief Markdown saved to {output_root}/adminhtml-forms/{Vendor}_{Module}-{entity}-form-{date}.md:
- Files generated/modified + surfaces enabled
- Test path + red→green evidence (or recorded gap)
- Post-gen commands:
bin/magento setup:upgrade(if di/routes/menu/acl changed), cache flush,setup:di:compilenote for production - The form's admin URL + ACL resource to grant
- Links to
reviewandtest-generate
Docs may now be stale. This change modified module code. Run
docs --module={Vendor}_{Module}to refresh the module's README, CHANGELOG, anddocs/*.md(technical reference, guides, and API references as applicable).
Inputs
/admin-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.
Outputs
{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 context/SKILL.md.
Output root (--docs-root)
This skill accepts --docs-root=<path> (see
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/. feature passes this so a feature
run's reports collect under its folder.
Reference Files
references/form-xml-anatomy.md—{entity}_form.xmlstructure + 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/dataTypemap; toggle, dynamicRows.references/validation-rules.md—required-entry,validate-number, custom + server-side.references/dependent-fields.md— show/hide viaimports/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.context/references/tdd-discipline.md— shared test-first loop (Phase 3A).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
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
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.
Acceptance Criteria
- The five naming-contract names agree; the form loads existing data and saves it.
- Button block classes exist for every
<button>referenced by the form. - DataProvider returns
[$id => [...]]and injectsDataPersistorInterface. acl.xmlhas notranslateattribute; every XML passesxmllint --noout.- A test asserting
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). - No Commerce-only wiring emitted on Open Source.
- Passes
reviewwith zero Critical/High findings.
Common Pitfalls Handled
| 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 |
Related Skills
| Phase | Skill |
| 0 | context |
| upstream | module-create — create the module first if absent |
| sibling | eav-attribute — add an attribute, then expose it on this form |
| downstream | test-generate — fuller unit/integration/MFTF coverage |
| gate | review — acceptance gate (zero Critical/High) |
| (caller) | feature — the admin-form slice of a feature |
In this repository35 other files it gives its agents.
- a11y-auditSkill
- admin-listingSkill
- auditSkill
- breeze-adaptSkill
- breeze-compatSkill
- breeze-themeSkill
- cli-commandSkill
- contextSkill
- data-migrationSkill
- debugSkill
- deploySkill
- docsSkill
- eav-attributeSkill
- extension-pointSkill
- featureSkill
- fixSkill
- frontendSkill
- graphqlSkill
- i18nSkill
- indexerSkill
- lintSkill
- marketplaceSkill
- message-queueSkill
- module-createSkill
- perf-auditSkill
- releaseSkill
- remediateSkill
- reviewSkill
- securitySkill
- system-configSkill
- test-generateSkill
- triageSkill
- upgradeSkill
- webapiSkill
- widgetSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

