Skip to content

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.

bash
npm run smart-admin -- generate-form contact

Options ​

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 command

Why 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.

json
{
  "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 ​

KeyRequiredWhat it is
pageYesThe 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.
fieldsYesThe form, in the order you write it. Emitted verbatim into a FieldDefinition[].
titleNoThe page heading. Defaults to "Add <form name>".
layoutNo"wizard" turns it into steps.
stepsWith a wizardEach { title, fields }, in the order you want them.

Field keys ​

KeyWhat it does
keyRequired. The field name.
typeRequired. One of text, email, password, number, textarea, select, combobox, multiselect, date, datetime, checkbox, switch, file, editor.
labelDefaults to the key, humanized: first_name becomes "First name". What you write becomes the base language's words; the key is generated for you.
placeholderLeave 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.
requiredAdds the rule and the marker.
defaultThe starting value.
optionsFor 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.
grid1 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:

json
{
  "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.ts

Plus 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.