generate-form
Generates a form page from a field list you declare. Flat, in sections, or as a wizard — with every validation rule already enforced.
npm run smart-admin -- generate-form contactOptions
Usage: smart-admin generate-form [options] <name>
Generate a form page from its block in smart-admin.form.json
Arguments:
name Form name — the block to read (e.g. "contact")
Options:
--dry-run Print the plan; write nothing
-h, --help display help for commandWhy it reads no API response
generate-form is a separate tool from generate, and it deliberately takes no sample row. A form is not derivable from what an endpoint returns: whether a field is required, how short it may be, which options it allows, which step it belongs to — none of that is ever on the wire.
The input file
Your forms go in smart-admin.form.json. Every top-level key is a form; keys starting with _ are notes.
{
"contact": {
"page": "/crm/contacts/quick-add",
"title": "New contact",
"fields": [
{
"key": "full_name",
"type": "text",
"label": "Full name",
"required": true,
"grid": 6,
"group": { "title": "Basics", "number": 1 },
"validations": { "minLength": 2, "maxLength": 80 }
},
{
"key": "email",
"type": "email",
"required": true,
"grid": 6,
"group": { "title": "Basics", "number": 1 }
},
{
"key": "stage",
"type": "select",
"required": true,
"default": "new",
"options": ["new", "qualified", "customer"],
"grid": 6,
"group": { "title": "Basics", "number": 1 }
},
{
"key": "owner_id",
"type": "combobox",
"label": "Account owner",
"options": "api",
"grid": 6,
"group": { "title": "Ownership", "number": 2 }
}
]
}
}Block keys
| Key | Required | What it is |
|---|---|---|
page | Yes | The route the form lives at. There is no default — a form with no address is a file nobody can open. The .vue file is derived from it: the last segment names the file, the form name names the module directory. |
fields | Yes | The form, in the order you write it. Emitted verbatim into a FieldDefinition[]. |
title | No | The page heading. Defaults to "Add <form name>". |
layout | No | "wizard" turns it into steps. |
steps | With a wizard | Each { title, fields }, in the order you want them. |
Field keys
| Key | What it does |
|---|---|
key | Required. The field name. |
type | Required. One of text, email, password, number, textarea, select, combobox, multiselect, date, datetime, checkbox, switch, file, editor. |
label | Defaults to the key, humanized: first_name becomes "First name". What you write becomes the base language's words; the key is generated for you. |
placeholder | Leave it out and the field says "Enter <label>", or "Select <label>" for a dropdown or a date — in every language, with no key of its own. |
required | Adds the rule and the marker. |
default | The starting value. |
options | For select, combobox and multiselect. Either the real values as an array, or the string "api". |
validations | { min, max, minLength, maxLength, pattern, message }. pattern is a regex source string. |
group | { "title": "Basics", "number": 1 }. Fields are grouped by number and the groups render in number order. |
grid | 1 to 12 columns. Only honored on a grouped form. |
"options": "api" gives you two placeholder options in the right shape and a note saying to swap them. Nothing is invented.
message replaces the default for that field's own constraints — min, max, minLength, maxLength, pattern. An empty required field always reads the shared, translated required message.
grid is ignored on a flat form. Leave group out entirely and the form renders as one flat grid, sized by field type so the two columns stay aligned. That is the house style across the eighty-one pages in the template that build on FormPageLayout.
Wizard
Set layout and list the steps:
{
"onboarding": {
"page": "/onboarding",
"title": "Set up your workspace",
"layout": "wizard",
"steps": [
{ "title": "Account", "fields": ["workspace", "work_email"] },
{ "title": "Team", "fields": ["team_size", "industry"] },
{ "title": "Finish", "fields": ["accept_terms"] }
],
"fields": [
{
"key": "workspace",
"type": "text",
"label": "Workspace name",
"required": true,
"validations": { "minLength": 3 }
},
{ "key": "work_email", "type": "email", "label": "Work email", "required": true },
{
"key": "team_size",
"type": "number",
"label": "How many people?",
"required": true,
"validations": { "min": 1, "max": 5000 }
},
{
"key": "industry",
"type": "select",
"options": ["software", "retail", "logistics", "education"]
},
{
"key": "accept_terms",
"type": "checkbox",
"label": "I accept the terms of service",
"required": true
}
]
}
}Fields are never distributed for you. Assign none and you get a plain form plus a notice. Assign some and leave others out, and the command refuses and names the ones you missed.
What it writes
src/modules/contact/
├── pages/
│ └── quick-add.vue
└── types/
└── contact.form.tsPlus the translation keys, in all nine locale files.
The real output
Form "contact"
from the "contact" block in smart-admin.form.json
page /crm/contacts/quick-add · 8 field(s) · translations under contact_form
file src/modules/contact/pages/quick-add.vue
Layout: sections — Basics · Ownership
in the order you numbered them, each field at the width you gave it.
created src/modules/contact/types/contact.form.ts
created src/modules/contact/pages/quick-add.vue
edited src/locales/*.json — 9 locale file(s)
24 keys · real words in "en" (the base), the same words
as placeholders in 8 other language(s) — translate them there.
✔ Form "contact" generated.
Next:
1. Restart the dev server. unplugin-vue-router only picks up /crm/contacts/quick-add on boot.
2. Fill in the two seams in src/modules/contact/pages/quick-add.vue:
// load the record to edit will be here
// submit will be here
3. Translate contact_form in src/locales/ — every language but the base holds
the base's own words as placeholders.
Not generated, because they cannot be known from here: the submit and
load requests, their auth, and where the form goes afterwards. The
validation IS generated — every rule you declared is already enforced.The wizard block reports its layout instead:
Layout: wizard — 3 step(s), fields as you assigned them.The two seams
One neutral form serves create and edit. Which one it is depends on your API, not on the template, so both requests are marked rather than guessed. The generated page carries a load the record to edit will be here marker and a submit will be here marker, and the command prints both paths when it finishes.
visibleIf is never generated either. A condition depends on values and permissions the CLI cannot see, and a wrong one hides a field on load. Add it by hand — Schema has the two forms it takes.
When it refuses
smart-admin.form.json has no "invoice" block.
It declares: contact, onboarding
Add one, then run this again. The shortest block that works:
"invoice": {
"page": "/invoice/new",
"fields": [{ "key": "name", "type": "text", "required": true }]
}It also refuses a wizard with fields left unassigned, and a select with no options.
Related
generate— the list half.- Forms — the engine behind the generated page.
- Schema — every field type and option.
- Form page layout — the shell it renders into.