- Published on
 
How to dynamically generate Zod Schemas | Zod Validation Guide
- Authors
 - Name
 - Ripal & Zalak
 
How to dynamically generate Zod Schemas in TypeScript
The Problem
Suppose you have a reusable form component in a React app built with react-hook-form. The form is defined dynamically, accepting an array of field configurations. You want to:
- Parse form inputs and validate them according to dynamic specifications.
 - Infer types for better developer experience during development.
 
Example:
// Manually coded schema
const schema = z.object({
  foo: z.string(),
  bar: z.number(),
})
type Fields = z.infer<typeof schema> // { foo: string; bar: number; }
// Dynamic schema configuration
const fields = [
  { name: 'foo', fieldType: z.string() },
  { name: 'bar', fieldType: z.number() },
]
const generateSchemaFromFields = (fields: FieldConfig[]) => {
  // Implementation needed
}
const generatedSchema = generateSchemaFromFields(fields)
The Solution
We can dynamically create a Zod schema using the field configurations. Here's a step-by-step solution:
Step 1: Define Field Configurations
Define the field configuration that includes the field name and validation rules:
const fields = [
  { name: 'foo', fieldType: z.string() },
  { name: 'bar', fieldType: z.number() },
] as { name: string; fieldType: z.ZodSchema }[]
Step 2: Generate Schema Dynamically
Convert the fields into a Zod schema object:
const generateSchemaFromFields = (fields: { name: string; fieldType: z.ZodSchema }[]) => {
  const schemaObject = Object.fromEntries(fields.map((field) => [field.name, field.fieldType]))
  return z.object(schemaObject)
}
const schema = generateSchemaFromFields(fields)
// Test the schema
const validInput = { foo: 'Hello', bar: 42 }
console.log(schema.safeParse(validInput).success) // true
const invalidInput = { foo: 42, bar: 'Oops' }
console.log(schema.safeParse(invalidInput).success) // false
Step 3: Handle Validation Rules Dynamically
Extend the solution to handle validation rules, such as min or max for strings or numbers:
function generateZodSchema(fields: { name: string; fieldType: string; validation?: any[] }[]) {
  const schema: Record<string, z.ZodType<any>> = {}
  fields.forEach((field) => {
    let fieldSchema: z.ZodType<any>
    switch (field.fieldType) {
      case 'string':
        fieldSchema = z.string()
        break
      case 'number':
        fieldSchema = z.number()
        break
      default:
        throw new Error(`Unsupported field type: ${field.fieldType}`)
    }
    // Apply validation rules
    field.validation?.forEach((rule) => {
      switch (rule.type) {
        case 'min':
          fieldSchema = fieldSchema.min(rule.value, rule.message)
          break
        case 'max':
          fieldSchema = fieldSchema.max(rule.value, rule.message)
          break
        default:
          throw new Error(`Unsupported validation type: ${rule.type}`)
      }
    })
    schema[field.name] = fieldSchema
  })
  return z.object(schema)
}
const dynamicFields = [
  {
    name: 'username',
    fieldType: 'string',
    validation: [{ type: 'min', value: 3, message: 'Must be at least 3 characters' }],
  },
  {
    name: 'age',
    fieldType: 'number',
    validation: [{ type: 'min', value: 18, message: 'Must be at least 18' }],
  },
]
const dynamicSchema = generateZodSchema(dynamicFields)
console.log(dynamicSchema.safeParse({ username: 'John', age: 25 }).success) // true
console.log(dynamicSchema.safeParse({ username: 'Jo', age: 17 }).success) // false
FAQs
Q: Why use Zod for schema generation?
Zod provides an intuitive, composable API for schema validation. It integrates seamlessly with react-hook-form using zodResolver.
Q: Can this handle nested schemas?
Yes, you can extend the generateZodSchema function to support nested field configurations by recursively creating schemas.
Q: Is TypeScript inference supported?
Yes, Zod allows you to infer TypeScript types using z.infer<typeof schema>, which ensures type safety in your application.
