Skip to main content

CLI Codegen

Generate static .tsx form components at build time using @zod-to-form/cli. This guide covers the current z2f generate command, the required z2f.config.ts, export selection, watch mode, server actions, and the programmatic API.

When to Use

Use the CLI codegen path when a project needs static, hand-readable .tsx form components generated from Zod v4 schemas at build time. It is best suited for production forms, design system integration, and cases where the generated code should be inspected, customized, and committed.

Prerequisites

  • Node.js >= 20
  • Zod v4 (zod@^4.0.0)
  • A z2f.config.ts file
  • A schema module that exports one or more Zod schemas

Installation

pnpm add -D @zod-to-form/cli zod

The package exposes both z2f and zod-to-form binaries. The examples below use the shorter z2f alias.

Basic Usage

1. Create z2f.config.ts

// z2f.config.ts
import { defineConfig } from '@zod-to-form/core';

export default defineConfig({
components: {
source: '@/components/ui',
preset: 'shadcn',
},
defaults: {
out: 'src/components',
overwrite: true,
mode: 'submit',
},
});

2. Define a Schema File

// src/schemas/user.ts
import { z } from 'zod';

export const userSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
role: z.enum(['admin', 'editor', 'viewer']),
bio: z.string().optional(),
newsletter: z.boolean().default(false),
});

3. Generate the Form Component

npx z2f generate \
--config z2f.config.ts \
--schema src/schemas/user.ts \
--export userSchema \
--out src/components/ \
--name UserForm

4. Generated Output

The generated src/components/UserForm.tsx imports your schema, React Hook Form, and the selected UI components. No @zod-to-form/* imports remain in the emitted file.

CLI Options

Required Flags

FlagDescription
--config <path>Path to z2f.config.ts (or .json)
--schema <path>Path to the TypeScript/JavaScript module containing the Zod schema exports

Optional Flags

FlagDefaultDescription
--export <name>Derived from configNamed export containing the root schema
--out <path>config.defaults.out or local defaultOutput directory or .tsx file path
--name <name>Derived from export nameGenerated component name
--mode <mode>config.defaults.mode or submitsubmit or auto-save
--ui <preset>config.defaults.ui or shadcnshadcn or html
--dry-runfalsePrint generated code without writing files
--server-actionfalseGenerate a Next.js server action alongside the form
--watchfalseWatch the schema file and regenerate on changes

Overwrite behavior is controlled by config.defaults.overwrite; there is no separate --force flag for generate.

Export Selection

--export is optional when your config already tells the CLI which exports to generate.

Resolution order:

  1. --export
  2. config.types
  3. exported schemas discovered from the module, filtered by config.include / config.exclude

That lets one command generate a single form, a curated list, or every matching export in a schema module.

Naming Conventions

--export valueDerived --nameOutput file
userSchemaUserFormUserForm.tsx
orderSchemaOrderFormOrderForm.tsx
loginDataLoginDataFormLoginDataForm.tsx

Override with --name:

npx z2f generate --config z2f.config.ts --schema src/user.ts --export userSchema --name ProfileEditor
# ProfileEditor.tsx

Generation Modes

Submit Mode

Generates a standard handleSubmit + onSubmit pattern:

npx z2f generate --config z2f.config.ts --schema src/schemas/user.ts --export userSchema

Auto-Save Mode

Generates a watch + useEffect pattern with onValueChange:

npx z2f generate \
--config z2f.config.ts \
--schema src/schemas/user.ts \
--export userSchema \
--mode auto-save

Multiple Exports from One Schema Module

If the schema module exports several forms, list them in config and omit --export:

// z2f.config.ts
import { defineConfig } from '@zod-to-form/core';

export default defineConfig({
components: {
source: '@/components/ui',
preset: 'shadcn',
},
types: ['UserSchema', 'AdminSchema'],
defaults: {
out: 'src/components',
overwrite: true,
},
});
npx z2f generate --config z2f.config.ts --schema src/schemas/index.ts

The CLI will emit both selected forms in one pass.

Config-Driven Schema Defaults

The CLI reads schemas[ExportName] from z2f.config.ts in two ways:

  1. Root-only generation settings such as name, mode, out, and serverAction
  2. Schema-identity defaults such as component and nested fields

That means reusable exported subschemas can carry their own default editor wherever they are reused.

import { defineConfig } from '@zod-to-form/core';
import * as schemaModule from './src/schemas';

export default defineConfig<typeof import('@/components/ui'), typeof schemaModule>({
components: {
source: '@/components/ui',
preset: 'shadcn',
},
schemas: {
ExpressionSchema: {
component: 'ExpressionEditor',
fields: {
language: { hidden: true },
},
},
WorkflowSchema: {
name: 'WorkflowForm',
},
},
});

If WorkflowSchema reuses the exact exported ExpressionSchema object, the generated form will render that nested schema with ExpressionEditor unless a more specific path override wins.

Server Actions

Generate a paired Next.js server action alongside the form:

npx z2f generate \
--config z2f.config.ts \
--schema src/schemas/user.ts \
--export userSchema \
--server-action

This produces both UserForm.tsx and user-form-action.ts.

Watch Mode

Regenerate automatically when the schema file changes:

npx z2f generate \
--config z2f.config.ts \
--schema src/schemas/user.ts \
--export userSchema \
--watch

--watch tracks the schema file passed to --schema. If your config changes, rerun the command.

Programmatic API

runGenerate(options)

import { runGenerate } from '@zod-to-form/cli';

const result = await runGenerate({
config: './z2f.config.ts',
schema: './src/schemas/user.ts',
export: 'userSchema',
out: './src/components/',
name: 'UserForm',
mode: 'submit',
ui: 'shadcn',
serverAction: true,
});

Returns:

PropertyTypeDescription
outputPathstringAbsolute path to the generated .tsx file
codestringGenerated TypeScript source
wroteFilebooleanWhether the file was written to disk
actionPathstring | undefinedPath to the generated server action file
actionCodestring | undefinedGenerated server action source

createProgram()

Returns a Commander.js Command instance for embedding in custom CLIs:

import { createProgram } from '@zod-to-form/cli';

const program = createProgram();
await program.parseAsync(['node', 'z2f', 'generate', '--config', 'z2f.config.ts', '--schema', 'src/schemas/user.ts']);

Relationship to Runtime

The CLI and runtime <ZodForm> share @zod-to-form/core, but the CLI consumes defineConfig() while runtime-only component binding happens through componentConfig. See Core Configuration and Runtime Component Config.