Skip to content

Quick start ​

bash
npm install -D formstand-cli
npx formstand-gen --help

The package installs a binary named formstand-gen. Your project supplies zod v4 and formstand itself; the CLI ships neither, and nothing it generates imports from the CLI.

Running one-shot without installing works too, but the runner must be told the package name, because the package (formstand-cli) and the binary (formstand-gen) differ: npx -p formstand-cli formstand-gen, pnpm --package=formstand-cli dlx formstand-gen, or yarn dlx -p formstand-cli formstand-gen. A bare yarn dlx formstand-gen asks the registry for a package that does not exist.

From a zod schema ​

Point it at a file that exports a schema. The schema is loaded and introspected at runtime using your copy of zod, so the output reflects exactly what the schema says, including optionality, nullability, defaults, and enum options.

bash
npx formstand-gen src/contactSchema.ts --out src/ContactForm.tsx

Given:

ts
export const contactSchema = z.object({
  name: z.string().min(1),
  email: z.string().email(),
  age: z.number().nullable(),
  role: z.enum(["admin", "user"]),
  tags: z.array(z.object({ label: z.string() })),
});

you get a component with a TextField per string, a NumberField for age, a SelectField carrying the enum's options, a useFieldArray section for tags with add and remove buttons, typed initialValues (note age: null, since nullability flows through to match emptyValue semantics), and a wired handleSubmit.

Which export gets used: --export NAME if you pass it, otherwise the default export, otherwise the sole zod-schema export in the file.

From a TypeScript type ​

No schema yet? Give it a type or interface and it generates both the zod schema and the form. You would need the schema anyway, since it is the runtime source of truth.

bash
npx formstand-gen src/types.ts --type Profile --out src/ProfileForm.tsx --schema-out src/profileSchema.ts

The type is expanded through the TypeScript compiler. Primitives, Date, string-literal unions (rendered as selects), arrays, nested objects, ?-optional and | null properties all map cleanly. A member's leading JSDoc description is carried into the generated schema as .describe(), which then becomes the control's helper text.

From a JSON Schema or OpenAPI document ​

bash
npx formstand-gen api.json --schema Order --out src/OrderForm.tsx

A .json input is read as a JSON Schema (the 2020-12 dialect) or an OpenAPI 3.x document. Like type mode, the input carries no runtime validator, so the CLI generates the zod schema beside the component. --schema picks the schema out of an OpenAPI document: a component name, or a full #/... JSON pointer for anything a name cannot reach, such as an operation's request body. A document with exactly one component schema needs no flag. See the keyword mapping for what translates and what degrades.

Where the output goes ​

You passWhat happens
nothingthe component streams to stdout, so you can pipe it anywhere
--out FILEwritten there, parent directories created as needed
--out DIR with --layout modulea feature folder, see Layouts
--schema-out FILE (type and .json modes)the generated zod schema goes here, defaulting to <schemaName>.ts next to --out

Notes and warnings go to stderr, so redirecting stdout stays clean. Writes are all-or-nothing: if any destination already exists and --force isn't set, nothing is written at all.

Iterating on a schema ​

bash
npx formstand-gen src/profileSchema.ts --watch --out src/ProfileForm.tsx

--watch regenerates whenever the input file changes. Paired with a config file holding your project defaults, editing the schema rewrites the form as you go, which is the schema-first loop the tool was built for.

Version floors

Kit output (--ui mui, shadcn, chakra, mantine, antd) imports parseNumberText and numberToInputText, so it needs formstand 0.3.0 or newer. --layout module needs 0.7 for createFormHooks. z.date() fields need 0.9. A discriminated union as an array's row item needs 0.16 (row-indexed useVariantField paths); unions and tuples nested deeper inside rows (row-object fields, nested-array items, tuple positions under holes) need 0.17. Kit output from formstand-cli 0.18 reads field.clearable (optional fields clear to undefined), so it needs formstand 0.18. Containers inside union variants generate as dotted variant sub-paths and need formstand 0.20; so do --tests specs, whose store-level cases import formstand/testing. Plain single-file output works on 0.2.0.

Try it without installing anything ​

The playground runs the real emitters in your browser:

  • Schema builder: build a schema by hand or paste TypeScript, then watch the generated files update as you flip --ui, --layout, --sections, --columns, --live, and --form-prop.
  • CLI command builder: pick your options and copy the exact formstand-gen command.

Built on zod and zustand. Released under the MIT License.