zod-v4

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.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "zod-v4" with this command: npx skills add bjornmelin/dev-skills/bjornmelin-dev-skills-zod-v4

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

v3v4
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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

streamdown

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

ai-sdk-core

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

ai-sdk-ui

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

notebook-ml-architect

No summary provided by upstream source.

Repository SourceNeeds Review