skills/zod-to-form-codegen/SKILL.md
Browser-safe code generation for Zod v4 form components Use when: Building a custom codegen pipeline that assembles `FormField[]` and needs the.... Also: zod, zod-v4, codegen, forms, form-generation, react-hook-form, schema-driven, template-generation, browser-safe, component-codegen, schema-to-tsx.
npx skillsauth add pradeepmouli/zod-to-form zod-to-form-codegenInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Browser-safe code generation for Zod v4 form components
The CLI reads your Zod schema and generates a complete, standalone .tsx form component. The output has no runtime dependency on any @zod-to-form package — it imports react, react-hook-form, your components, and zod only when refines or transforms are present. Stop using zod-to-form entirely and the generated code keeps working.
The runtime <ZodForm> uses the same @zod-to-form/core walker and the same z2f.config.ts, so component overrides, props, sections, and field ordering carry across. Start with runtime for rapid iteration, eject to codegen for production — nothing changes except who owns the output.
The walker knows both the schema constraints and which component renders each field. This enables three levels of progressive optimization that work in both runtime and codegen:
Level 1 — Decompose tree. Inline all per-field Zod calls as atomic safeParse on hoisted schema fragments (created once at module load, not per keystroke). Eliminate zodResolver. Object-level refines/transforms collected into a schemaLite and called via safeParse on submit.
Level 2 — Native rules. Replace inlined Zod calls with native RHF register() rules where supported: min, max, minLength, maxLength, pattern (including extracted email/uuid/url regexes), required, step. Error messages extracted from the Zod schema at build time. Component-enforced types (enum, boolean, literal) need no validation at all. After L2, Zod calls only remain for fields with refine() or transform(). If none remain and schemaLite is empty, the zod import can be dropped from codegen output.
Level 3 — Cross-field UX. Convert analyzable cross-field superRefine — both inline on fields and top-level on the schema object — to watch() + validate for real-time feedback instead of submit-time errors.
The 'shadcn' preset handles controlled components — Select, Checkbox, Switch, RadioGroup — with no custom widgets or renderers. Each component declares its prop mapping once:
Select: { controlled: true, props: { onValueChange: 'field.onChange' } }
Checkbox: { controlled: true, props: { checked: 'field.value', onCheckedChange: 'field.onChange' } }
Values that match a known RHF field expression (field.onChange, field.value, field.onBlur, field.ref) are resolved from the React Hook Form controller. Everything else passes through as a literal. One props Record for both — no separate propMap.
In RJSF you write a custom widget per controlled component. In JSON Forms you write a custom renderer. Here you write two lines of config.
No schema conversion. No intermediate representation invented by the library. zod-to-form reads Zod v4's own internal API:
_zod.def for schema structure (types, shapes, discriminators)_zod.bag for constraint values (min, max, patterns, formats)z.registry() for metadata (title, description, examples, deprecated).meta() for UI-specific annotations (component overrides, props, hidden, order)When Zod v4 adds new constraint types, the walker picks them up without code changes.
The walker handles Zod discriminated unions natively. Field visibility and validation are always in sync because both derive from the same schema — not from a parallel rule system that can drift.
const schema = z.discriminatedUnion('type', [
z.object({ type: z.literal('personal'), firstName: z.string(), lastName: z.string() }),
z.object({ type: z.literal('business'), companyName: z.string(), taxId: z.string() }),
]);
// Renders a select for 'type'. Switching to 'business' removes firstName/lastName
// from the form state and validation, adds companyName/taxId. No rule system needed.
The walker dispatches each schema node through two registries:
Processor registry — maps Zod types to components. One processor per def.type. Override or extend for your own components.
Optimizer registry — optimizes validation per field. Chained (1:N per type) because L1, L2, L3 are independent transforms. Register custom optimizers for components that enforce constraints the library doesn't know about:
const dateRangeOptimizer: FormOptimizer = (schema, ctx, field) => {
if (field.component === 'DateRangePicker') {
field.validation = { mode: 'component-enforced' };
field.zodSchema = undefined;
}
};
walkSchema(schema, {
optimization: { level: 2, optimizers: { date: [dateRangeOptimizer] } }
});
fields and arrayItems on FieldConfig are typed to the Zod schema shape. Your editor autocompletes field names and catches typos at compile time.
export default defineConfig({
schemas: {
userSchema: {
fields: {
name: { component: 'Input', order: 1 },
addresses: {
arrayItems: {
fields: {
street: { component: 'Input' },
city: { component: 'Combobox', props: { options: cities } },
}
}
},
// typo: { ... } ← TypeScript error: 'typo' does not exist on UserSchema
}
}
}
});
Group fields from a flat schema into visual sections without changing the schema. Fields sharing the same section key are rendered as a group.
fields: {
billingName: { section: 'billing', order: 1 },
billingCard: { section: 'billing', order: 2 },
shippingAddress: { section: 'shipping', order: 1 },
shippingCity: { section: 'shipping', order: 2 },
}
RJSF and JSON Forms require restructuring your schema or maintaining a separate layout document to achieve this.
pnpm add @zod-to-form/core @zod-to-form/react zod react react-hook-form @hookform/resolvers
Use this skill when:
FormField[] and needs the TSX string → use generateFormComponentgenerateFormComponentresolveFieldMappingresolveFieldMappingDo NOT use when:
runGenerate() from @zod-to-form/cli instead (generateFormComponent)compileTarget wraps this and handles esbuild transformation (generateFormComponent)resolveFieldMapping)API surface: 8 functions, 1 constants
generateFormComponent with a stale fields array from a previous schema version — there is no cache invalidation; callers must re-run walkSchema on schema changeconfigHash from @zod-to-form/core on the config object insteadsource: 'none' means the field has no component — the schema walker may have inferred one; resolveFieldMapping only resolves user-provided config overrides2 configuration interfaces — see references/config.md for details.
Codegen: generateFormComponent (Generate a React form component as a TypeScript string from FormField[]), resolveFieldMapping (Resolve the component name and override config for a single FormField key)
Templates: getFileHeader (Generate the import block for a form component file), renderField (Render a single FormField to its plain-HTML JSX string), registerPathExpr (Produce the correct register(), generateSchemaLiteFile(Generate the content of a), getFieldTemplateSource (Return the source code for the preset's FieldTemplate React component)
Config Templates: buildConfigSource (Generate a z2f) **field-templates:** PRESET_TEMPLATE_IMPORTS` (Components that each preset's field template imports from...)
Load these on demand — do NOT read all at once:
references/functions.md for full signatures, parameters, and return typesreferences/variables.mdreferences/config.md for all settings and defaultstools
Use when working with zod-to-form (core, react, cli, codegen, vite).
tools
Vite plugin for zod-to-form — transforms ?z2f imports into generated form components and optionally replaces <ZodForm> JSX call sites with generated components at build time Use when: You want `import SignupForm from './signup.schema?z2f'` to Just Work in a.... Also: vite, vite-plugin, zod, zod-v4, codegen, forms, form-generation, schema-driven, react-hook-form, build-plugin, jsx-transform.
development
Runtime <ZodForm> renderer for Zod v4 schemas Use when: You need form rendering in storybook, playgrounds, or low-traffic admin UIs —.... Also: zod, zod-v4, react, forms, form-generation, react-hook-form, schema-driven, dynamic-forms, form-renderer, hookform-resolver, zod-form-renderer.
development
Schema walker and processor registry for Zod v4 form generation Use when: You want per-field validation instead of whole-form validation. Also: zod, zod-v4, forms, form-generation, schema, schema-walker, processor-registry, react-hook-form, schema-driven, form-schema, zod-registry.