Zod Reference

Zod is a TypeScript-first schema validation library. Use it for runtime validation, parsing data, and inferring TypeScript types from schemas.

Import


// Define schema
const taskSchema = z.object({
  title: z.string().min(1, { error: "Title is required" }).max(100, { error: "Title too long" }),
  priority: z.enum(["low", "medium", "high"]),
  dueDate: z.string().optional(),
});

type Task = z.infer<typeof taskSchema>;

export default function App() {
  const [title, setTitle] = React.useState("");
  const [priority, setPriority] = React.useState<"low" | "medium" | "high">("medium");
  const [errors, setErrors] = React.useState<Record<string, string>>({});
  const [tasks, setTasks] = React.useState<Task[]>([]);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    
    const result = taskSchema.safeParse({ title, priority });
    
    if (!result.success) {
      // Convert Zod errors to a simple object
      const fieldErrors: Record<string, string> = {};
      result.error.issues.forEach(issue => {
        const field = issue.path[0] as string;
        fieldErrors[field] = issue.message;
      });
      setErrors(fieldErrors);
      return;
    }
    
    // Valid data
    setTasks(prev => [...prev, result.data]);
    setTitle("");
    setErrors({});
  };

  return (
    <div>
      <div>
        <div>
          <h3>Add task</h3>
        </div>
        <div>
          <form onSubmit={handleSubmit} className="space-y-4">
            <div className="space-y-2">
              <label htmlFor="title">Title</label>
              <input
                id="title"
                value={title}
                onChange={e => setTitle(e.target.value)}
              />
              {errors.title && (
                <p className="text-sm text-destructive">{errors.title}</p>
              )}
            </div>
            <button type="submit">Add</button>
          </form>
        </div>
      </div>
      
      {tasks.map((task, i) => (
        <div key={i}>
          <div className="pt-4">
            <p>{task.title}</p>
            <p className="text-sm text-muted-foreground">{task.priority}</p>
          </div>
        </div>
      ))}
    </div>
  );
}

Use with AI Object Generation

Zod schemas work with the useAIObject hook for structured AI responses:

import { z } from "zod";
import { useAIObject } from "@/hooks/use-ai-object";

const recipeSchema = z.object({
  name: z.string(),
  ingredients: z.array(z.object({
    item: z.string(),
    amount: z.string(),
  })),
  steps: z.array(z.string()),
});

const { object, generate, isLoading } = useAIObject({
  schema: recipeSchema,
});

Best Practices

  1. Define schemas at module level - Reuse schemas across components
  2. Use z.infer - Derive types from schemas to keep them in sync
  3. Use safeParse in forms - Handle validation errors gracefully
  4. Use the error parameter - Provide user-friendly error messages
  5. Use top-level formats - Prefer z.email() over z.string().email() for tree-shaking
  6. Use discriminated unions - For type-safe handling of different object shapes
  7. Use .meta() for documentation - Attach metadata for JSON Schema generation