Zod
See Official Document.
Basic Usage
typescript
import { z } from "zod";
// define schema
const PlayerSchema = z.object({
username: z.string(),
xp: z.number()
});
// extract the inferred type
type Player = z.infer<typeof PlayerSchema>;1. Primitive Types
typescript
z.string();
z.number();
z.bigint();
z.boolean();
z.date();
z.symbol();
// Empty types
z.undefined();
z.null();
z.void(); // accepts undefined
// Catch-all types
z.any();
z.unknown();
// Never type (fails on every validation)
z.never();2. Strings
String schemas have built-in validation methods.
typescript
z.string().max(5);
z.string().min(5);
z.string().length(5);
// v4: use top-level functions instead of the old chained .email()/.url()/etc.
z.email();
z.url();
z.emoji();
z.uuid();
z.cuid();
z.cuid2();
z.ulid();
z.string().regex(/^[a-z]+$/);
z.string().includes("tuna");
z.string().startsWith("https://");
z.string().endsWith(".com");
z.iso.datetime(); // ISO 8601
z.ipv4(); // or z.ipv6()3. Numbers
typescript
z.number().gt(5); // > 5
z.number().gte(5); // >= 5
z.number().lt(5); // < 5
z.number().lte(5); // <= 5
z.number().int(); // Must be an integer
z.number().positive(); // > 0
z.number().nonnegative();// >= 0
z.number().negative(); // < 0
z.number().nonpositive();// <= 0
z.number().multipleOf(5);4. Modifiers
You can chain modifiers to allow null/undefined or provide default values.
typescript
const schema = z.string();
schema.optional(); // string | undefined
schema.nullable(); // string | null
schema.nullish(); // string | null | undefined
// Default values
z.string().default("Guest");
z.number().default(Math.random);
// Catch errors and return a fallback value
z.string().catch("fallback_value");5. Objects
typescript
const User = z.object({
username: z.string(),
age: z.number().optional(),
});
// Unknown Keys Handling
User.passthrough(); // Keep unknown keys
User.strict(); // Fail if unknown keys are present
User.strip(); // Remove unknown keys (default behavior)
// Object Methods
const PartialUser = User.partial(); // Shallow only; top-level keys become optional
const RequiredUser = User.required();
const NameOnly = User.pick({ username: true });
const AgeOnly = User.omit({ username: true });
// Combine objects (use .extend() for both; .merge() is deprecated in v4)
const Admin = User.extend({ role: z.literal("ADMIN") });
const Merged = User.extend(z.object({ email: z.string() }).shape);6. Arrays
typescript
const stringArray = z.array(z.string());
// equivalent to z.string().array()
stringArray.nonempty(); // Must contain at least one element
stringArray.min(5); // Minimum 5 elements
stringArray.max(10); // Maximum 10 elements
stringArray.length(5); // Exactly 5 elements7. Enums & Literals
typescript
// Literal values (exact match)
const tuna = z.literal("tuna");
const twelve = z.literal(12);
// Zod Enums (recommended)
const FishEnum = z.enum(["Salmon", "Tuna", "Trout"]);
FishEnum.parse("Salmon"); // => "Salmon"
// Extract values
FishEnum.options; // ["Salmon", "Tuna", "Trout"]
// Native TypeScript Enums (z.enum() accepts these directly since v4; z.nativeEnum() is deprecated)
enum Fruits { Apple, Banana }
const FruitEnum = z.enum(Fruits);8. Unions & Intersections
typescript
// OR (Union)
const stringOrNumber = z.union([z.string(), z.number()]);
// or: z.string().or(z.number())
// Discriminated Unions (better performance for complex objects)
const MyUnion = z.discriminatedUnion("status", [
z.object({ status: z.literal("success"), data: z.string() }),
z.object({ status: z.literal("failed"), error: z.instanceof(Error) }),
]);
// AND (Intersection)
const Person = z.object({ name: z.string() });
const Employee = z.object({ role: z.string() });
const EmployedPerson = z.intersection(Person, Employee);
// or: Person.and(Employee)9. Records, Tuples, Sets, & Maps
typescript
// Record: object with unknown keys but specific value types
// v4 requires an explicit key schema (single-arg form was removed)
z.record(z.string(), z.number()); // Record<string, number>
z.record(z.string().min(1), z.number()); // Record with key validation
// Tuple: fixed length array
z.tuple([z.string(), z.number(), z.boolean()]);
// Set
z.set(z.string());
// Map
z.map(z.string(), z.number());10. Parsing / Validation Methods
typescript
const schema = z.string();
// .parse() - Throws an error if invalid
schema.parse("tuna"); // Returns "tuna"
schema.parse(12); // Throws ZodError
// .safeParse() - Returns an object, DOES NOT throw
const result = schema.safeParse("tuna");
if (result.success) {
console.log(result.data); // "tuna"
} else {
console.log(result.error); // ZodError instance
}11. Type Inference
Extract the TypeScript type from a Zod schema using z.infer.
typescript
const UserSchema = z.object({
name: z.string(),
age: z.number().optional(),
});
type User = z.infer<typeof UserSchema>;
/* type User = {
name: string;
age?: number | undefined;
}
*/12. Custom Validation (.refine)
Add custom validation logic.
typescript
const passwordSchema = z.string().refine((val) => val.length >= 8, {
message: "Password must be at least 8 characters long",
});
// Advanced refinement (checking multiple fields)
const Form = z.object({
password: z.string(),
confirm: z.string()
}).refine((data) => data.password === data.confirm, {
message: "Passwords don't match",
path: ["confirm"], // Sets the error path to the 'confirm' field
});13. Preprocessing / Coercion
Transform data before parsing or force coercion.
typescript
// Coercion (automatically convert primitives)
z.coerce.string(); // String(val)
z.coerce.number(); // Number(val)
z.coerce.boolean(); // Boolean(val)
z.coerce.date(); // new Date(val)
// Preprocess (custom transformation BEFORE parsing)
const stringToNumber = z.preprocess(
(val) => (typeof val === "string" ? parseInt(val, 10) : val),
z.number()
);
// Transform (custom transformation AFTER parsing)
const nameSchema = z.string().transform((val) => val.toUpperCase());
nameSchema.parse("john"); // => "JOHN"