Expert guidance for Zod v4 schema validation in TypeScript. Use when designing schemas, migrating from Zod 3, handling validation errors, generating JSON Schema/OpenAPI, using codecs/transforms, or integrating with React Hook Form, tRPC, Hono, or Next.js. Covers all Zod v4 APIs including top-level string formats, strictObject/looseObject, metadata, registries, branded types, and recursive schemas.
Installation
Details
Usage
After installing, this skill will be available to your AI coding assistant.
Verify installation:
npx agent-skills-cli listSkill Instructions
name: zod-v4 description: Expert guidance for Zod v4 schema validation in TypeScript. Use when designing schemas, migrating from Zod 3, handling validation errors, generating JSON Schema/OpenAPI, using codecs/transforms, or integrating with React Hook Form, tRPC, Hono, or Next.js. Covers all Zod v4 APIs including top-level string formats, strictObject/looseObject, metadata, registries, branded types, and recursive schemas.
Zod v4 Schema Validation
Quick Start
pnpm add zod@^4.3.5
import { z } from 'zod';
// Define schema
const User = z.object({
name: z.string().min(1),
email: z.email(),
age: z.number().positive(),
});
// Parse (throws on error)
const user = User.parse({ name: "Alice", email: "alice@example.com", age: 30 });
// Safe parse (returns result)
const result = User.safeParse(data);
if (result.success) {
result.data; // validated
} else {
console.log(z.prettifyError(result.error));
}
// Type inference
type User = z.infer<typeof User>;
Versioning + Imports (v4.3.5)
- Use
import { z } from "zod"for v4 (package root now exports v4). - Use
import * as z from "zod/mini"for Zod Mini. - Use
import * as z from "zod/v3"only if you must stay on v3.
Workflow: Determine Task Type
Designing new schemas? → Read API Reference
Migrating from Zod 3? → Read Migration Guide
Working with codecs, errors, JSON Schema, or metadata? → Read Advanced Features
Integrating with frameworks (RHF, tRPC, Hono, Next.js)? → Read Ecosystem Patterns
Key v4 Concepts
Top-Level String Formats
v4 moved string validators to top-level functions:
// v4 style (preferred)
z.email()
z.uuid()
z.url()
z.ipv4()
z.ipv6()
z.iso.date()
z.iso.datetime()
// v3 style (deprecated but works)
z.string().email()
Object Variants
z.object({}) // Allows unknown keys (default)
z.strictObject({}) // Rejects unknown keys
z.looseObject({}) // Explicitly allows unknown keys
Unified Error Parameter
// String message
z.string().min(5, { error: "Too short" });
// Function for dynamic messages
z.string({
error: (iss) => iss.input === undefined ? "Required" : "Invalid"
});
Type Inference
const Schema = z.object({ name: z.string() });
type Schema = z.infer<typeof Schema>;
// For transforms, get input/output separately
const Transformed = z.string().transform(s => s.length);
type Input = z.input<typeof Transformed>; // string
type Output = z.output<typeof Transformed>; // number
Common Patterns
Discriminated Unions
const Event = z.discriminatedUnion("type", [
z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
z.object({ type: z.literal("keypress"), key: z.string() }),
]);
Exhaustive Records
const Status = z.enum(["pending", "active", "done"]);
// All keys required
z.record(Status, z.number()) // { pending: number; active: number; done: number }
// Keys optional
z.partialRecord(Status, z.number()) // { pending?: number; active?: number; done?: number }
Recursive Schemas
const Category = z.object({
name: z.string(),
get subcategories() { return z.array(Category) }
});
Branded Types
const UserId = z.string().brand<"UserId">();
const PostId = z.string().brand<"PostId">();
type UserId = z.infer<typeof UserId>;
// Cannot assign UserId to PostId
Transforms and Pipes
// Transform
z.string().transform(s => s.toUpperCase())
// Pipe (chain schemas)
z.pipe(
z.string(),
z.coerce.number(),
z.number().positive()
)
Default Values
// Output default (v4)
z.string().default("guest")
// Input default (pre-transform)
z.string().transform(s => s.toUpperCase()).prefault("hello")
// Missing => "HELLO"
Error Handling
Pretty Print
const result = schema.safeParse(data);
if (!result.success) {
console.log(z.prettifyError(result.error));
// ✖ Invalid email
// → at email
}
Flat Structure (Forms)
const flat = z.flattenError(result.error);
// { formErrors: [], fieldErrors: { email: ["Invalid email"] } }
Tree Structure (Nested)
const tree = z.treeifyError(result.error);
// { properties: { email: { errors: ["Invalid email"] } } }
JSON Schema / OpenAPI
const schema = z.object({
name: z.string(),
email: z.email(),
}).meta({ id: "User", title: "User" });
// Generate JSON Schema
const jsonSchema = z.toJSONSchema(schema);
// For OpenAPI 3.0
z.toJSONSchema(schema, { target: "openapi-3.0" });
// Using registry for multiple schemas
z.globalRegistry.add(schema, schema.meta());
const allSchemas = z.toJSONSchema(z.globalRegistry);
v3 to v4 Migration Quick Reference
| v3 | v4 |
|---|---|
z.string().email() | z.email() |
z.nativeEnum(MyEnum) | z.enum(MyEnum) |
{ message: "..." } | { error: "..." } |
.strict() | z.strictObject({}) |
.passthrough() | z.looseObject({}) |
.merge(other) | .extend(other.shape) |
z.record(valueSchema) | z.record(z.string(), valueSchema) |
.deepPartial() | Nest .partial() manually |
error.format() | z.treeifyError(error) |
error.flatten() | z.flattenError(error) |
Breaking Changes
- Numbers: No
Infinity, stricter.safe()and.int() - UUID: RFC 4122 compliant (use
z.guid()for permissive) - Defaults in optional:
z.string().default("x").optional()now applies default - z.unknown(): No longer implicitly optional
- Error precedence: Schema-level wins over global
Run codemod: npx zod-v3-to-v4
Framework Integration Quick Start
React Hook Form
import { zodResolver } from '@hookform/resolvers/zod';
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(schema),
});
tRPC
publicProcedure
.input(z.object({ id: z.string() }))
.query(({ input }) => getById(input.id))
Hono
import { zValidator } from '@hono/zod-validator';
app.post('/users', zValidator('json', schema), (c) => {
const data = c.req.valid('json');
});
Next.js Server Actions
'use server';
const result = schema.safeParse(Object.fromEntries(formData));
if (!result.success) {
return { errors: z.flattenError(result.error).fieldErrors };
}
Reference Files
- API Reference - All schema types, methods, and validation APIs
- Advanced Features - Codecs, error handling, metadata, JSON Schema
- Migration Guide - Complete v3 to v4 migration reference
- Ecosystem Patterns - Framework integrations and organization patterns
More by BjornMelin
View allExpert guidance for building AI agents with ToolLoopAgent (AI SDK v6+). Use when creating agents, configuring stopWhen/prepareStep, callOptionsSchema/prepareCall, dynamic tool selection, tool loops, or agent workflows (sequential, routing, evaluator-optimizer, orchestrator-worker). Triggers: ToolLoopAgent, agent loop, stopWhen, stepCountIs, prepareStep, callOptionsSchema, prepareCall, hasToolCall, InferAgentUIMessage, agent workflows.
Expert guidance for building chat UIs with AI SDK React hooks. Use when: (1) Building chatbots with useChat hook, (2) Implementing tool UIs with server/client execution, (3) Handling message persistence and stream resumption, (4) Creating generative UI with React components, (5) Integrating with Next.js/Node/Fastify/Nest backends, (6) Using useObject for streaming structured data. Triggers: useChat, useObject, useCompletion, chat UI, chatbot, generative UI, message persistence, @ai-sdk/react, UIMessage, tool parts, streaming UI, toUIMessageStreamResponse.
Production-ready Supabase integration patterns for Next.js/React/TypeScript applications. Use when working with Supabase for (1) SSR authentication with @supabase/ssr, (2) Database operations and migrations, (3) Row Level Security (RLS) policies, (4) Storage buckets and file uploads, (5) Realtime channels and presence, (6) Edge Functions with Deno, (7) pgvector embeddings and semantic search, (8) Vercel deployment and connection pooling, (9) CLI operations (type generation, migrations). Triggers on Supabase client setup, auth patterns, RLS policies, storage uploads, realtime subscriptions, Edge Functions, vector search, or Vercel+Supabase deployment.
World-class Vitest QA/test engineer for TypeScript + Next.js (local + CI performance focused)
