Basic Usage

Install

npm install zod

Define a Schema

import { z } from "zod";

const ProductSchema = z.object({
  name: z.string(),
  price: z.number().nonnegative(),
  tags: z.array(z.string()),
});

Validate Data

// .parse() throws on invalid data
const product = ProductSchema.parse({
  name: "Widget",
  price: 9.99,
  tags: ["tools"],
});

// .safeParse() returns a result object (no throw)
const result = ProductSchema.safeParse(input);
if (result.success) {
  console.log(result.data); // typed as Product
} else {
  console.log(result.error.issues); // validation errors
}

Tip: Use .safeParse() in production code. It’s easier to handle errors without try/catch.

Infer TypeScript Types

type Product = z.infer<typeof ProductSchema>;
// { name: string; price: number; tags: string[] }

// Use the type like any other TypeScript type
function displayProduct(product: Product) {
  console.log(`${product.name}: $${product.price}`);
}

Common Patterns

Form validation

const LoginSchema = z.object({
  email: z.string().email("Invalid email"),
  password: z.string().min(8, "Password must be at least 8 characters"),
});

const result = LoginSchema.safeParse(formData);
if (!result.success) {
  const errors = result.error.flatten().fieldErrors;
  // { email?: string[], password?: string[] }
}

API response validation

const ApiResponse = z.object({
  data: z.array(ProductSchema),
  total: z.number(),
  page: z.number(),
});

const response = await fetch("/api/products");
const validated = ApiResponse.parse(await response.json());

Environment variables

const EnvSchema = z.object({
  DATABASE_URL: z.string().url(),
  PORT: z.coerce.number().default(3000),
  NODE_ENV: z.enum(["development", "production", "test"]),
});

const env = EnvSchema.parse(process.env);

Gotcha: z.coerce.number() converts strings to numbers (useful for env vars and query params). Plain z.number() rejects strings.