↑
Primitive Types
String Validators
Number Validators
Composite Types
Modifiers
Object Utilities
Transformations Refinements
Parsing
Type Inference
Primitive Types
Introduction
Zod is a TypeScript-first schema declaration and validation library.
Primitive types are the foundational schemas that validate basic JavaScript values.
All primitive schemas are created via factory functions on the z namespace.
z.string()
Signature: z.string(): ZodString
Validates that a value is of type string.
const schema = z.string();
schema.parse("hello"); // ✅ Returns "hello"
schema.parse(""); // ✅ Returns ""
schema.parse(123); // ❌ Throws ZodError: Expected string, received number
schema.parse(null); // ❌ Throws ZodError: Expected string, received null
z.number()
Signature: z.number(): ZodNumber
Validates that a value is of type number. Rejects NaN by default.
const schema = z.number();
schema.parse(42); // ✅ Returns 42
schema.parse(3.14); // ✅ Returns 3.14
schema.parse(-100); // ✅ Returns -100
schema.parse(Infinity); // ✅ Returns Infinity
schema.parse("42"); // ❌ Throws ZodError: Expected number, received string
schema.parse(NaN); // ❌ Throws ZodError: Expected number, received nan
z.boolean()
Signature: z.boolean(): ZodBoolean
Validates that a value is true or false.
const schema = z.boolean();
schema.parse(true); // ✅ Returns true
schema.parse(false); // ✅ Returns false
schema.parse(1); // ❌ Throws ZodError: Expected boolean, received number
schema.parse("true"); // ❌ Throws ZodError: Expected boolean, received string
z.bigint()
Signature: z.bigint(): ZodBigInt
Validates that a value is a JavaScript BigInt.
const schema = z.bigint();
schema.parse(10n); // ✅ Returns 10n
schema.parse(BigInt(999)); // ✅ Returns 999n
schema.parse(10); // ❌ Throws ZodError: Expected bigint, received number
schema.parse("10"); // ❌ Throws ZodError: Expected bigint, received string
z.date()
Signature: z.date(): ZodDate
Validates that a value is a JavaScript Date object. Rejects invalid dates.
const schema = z.date();
schema.parse(new Date()); // ✅ Returns Date object
schema.parse(new Date("2024-01-15")); // ✅ Returns Date object
schema.parse(new Date("invalid")); // ❌ Throws ZodError: Invalid date
schema.parse("2024-01-15"); // ❌ Throws ZodError: Expected date, received string
schema.parse(1705276800000); // ❌ Throws ZodError: Expected date, received number
z.undefined()
Signature: z.undefined(): ZodUndefined
Validates that a value is exactly undefined.
const schema = z.undefined();
schema.parse(undefined); // ✅ Returns undefined
schema.parse(void 0); // ✅ Returns undefined
schema.parse(null); // ❌ Throws ZodError: Expected undefined, received null
schema.parse(""); // ❌ Throws ZodError: Expected undefined, received string
z.null()
Signature: z.null(): ZodNull
Validates that a value is exactly null.
const schema = z.null();
schema.parse(null); // ✅ Returns null
schema.parse(undefined); // ❌ Throws ZodError: Expected null, received undefined
schema.parse(0); // ❌ Throws ZodError: Expected null, received number
z.any()
Signature: z.any(): ZodAny
Accepts any value. No validation is performed.
The inferred TypeScript type is any.
Note: Use sparingly — defeats the purpose of type safety.
const schema = z.any();
schema.parse("hello"); // ✅ Returns "hello"
schema.parse(123); // ✅ Returns 123
schema.parse(null); // ✅ Returns null
schema.parse(undefined); // ✅ Returns undefined
schema.parse({ a: 1 }); // ✅ Returns { a: 1 }
z.unknown()
Signature: z.unknown(): ZodUnknown
Accepts any value (like z.any()), but infers TypeScript type as unknown.
Forces you to narrow the type before use.
Prefer z.unknown() over z.any() for type-safe handling of arbitrary data.
const schema = z.unknown();
schema.parse("hello"); // ✅ Returns "hello"
schema.parse(123); // ✅ Returns 123
schema.parse(null); // ✅ Returns null
// TypeScript behavior:
const result = schema.parse(someData);
result.toUpperCase(); // ❌ TypeScript error: 'result' is of type 'unknown'
// Must narrow first:
if (typeof result === "string") {
result.toUpperCase(); // ✅ OK
}
z.never()
Signature: z.never(): ZodNever
Always fails validation.
Useful for exhaustive checks or marking impossible branches.
const schema = z.never();
schema.parse("anything"); // ❌ Throws ZodError
schema.parse(undefined); // ❌ Throws ZodError
schema.parse(null); // ❌ Throws ZodError
// Practical use: exhaustive switch
type Status = "active" | "inactive";
function handleStatus(status: Status) {
switch (status) {
case "active": return "on";
case "inactive": return "off";
default:
z.never().parse(status); // Compile-time + runtime check for unhandled cases
}
}
z.void()
Signature: z.void(): ZodVoid
Validates that a value is undefined.
Semantically used for functions that return nothing.
Note: Functionally identical to z.undefined(), but conveys intent for return types.
const schema = z.void();
schema.parse(undefined); // ✅ Returns undefined
schema.parse(null); // ❌ Throws ZodError: Expected void, received null
schema.parse("hello"); // ❌ Throws ZodError: Expected void, received string
// Typical use: function return type
const fnSchema = z.function().args(z.string()).returns(z.void());
String Validators
Introduction
All methods below are chained on a ZodString instance (returned by z.string()).
Some are validators (check conditions), others are transformers (modify output).
.min()
Signature: ZodString.min(length: number, message?: string): ZodString
Requires string to have at least length characters.
const schema = z.string().min(3);
schema.parse("hello"); // ✅ Returns "hello"
schema.parse("abc"); // ✅ Returns "abc"
schema.parse("ab"); // ❌ ZodError: String must contain at least 3 character(s)
schema.parse(""); // ❌ ZodError
// Custom message
z.string().min(3, "Too short").parse("ab"); // ❌ ZodError: Too short
.max()
Signature: ZodString.max(length: number, message?: string): ZodString
Requires string to have at most length characters.
const schema = z.string().max(5);
schema.parse("hi"); // ✅ Returns "hi"
schema.parse("hello"); // ✅ Returns "hello"
schema.parse("hello!"); // ❌ ZodError: String must contain at most 5 character(s)
.length()
Signature: ZodString.length(length: number, message?: string): ZodString
Requires string to have exactly length characters.
const schema = z.string().length(4);
schema.parse("abcd"); // ✅ Returns "abcd"
schema.parse("abc"); // ❌ ZodError: String must contain exactly 4 character(s)
schema.parse("abcde"); // ❌ ZodError
.email()
Signature: ZodString.email(message?: string): ZodString
Validates that string is a valid email format.
const schema = z.string().email();
schema.parse("user@example.com"); // ✅ Returns "user@example.com"
schema.parse("test@sub.domain.org"); // ✅ Returns "test@sub.domain.org"
schema.parse("invalid"); // ❌ ZodError: Invalid email
schema.parse("user@"); // ❌ ZodError: Invalid email
schema.parse("@example.com"); // ❌ ZodError: Invalid email
.url()
Signature: ZodString.url(message?: string): ZodString
Validates that string is a valid URL.
const schema = z.string().url();
schema.parse("https://example.com"); // ✅
schema.parse("http://localhost:3000"); // ✅
schema.parse("ftp://files.example.com"); // ✅
schema.parse("example.com"); // ❌ ZodError: Invalid url
schema.parse("not a url"); // ❌ ZodError: Invalid url
.uuid()
Signature: ZodString.uuid(message?: string): ZodString
Validates that string is a valid UUID (v1–v5 format).
const schema = z.string().uuid();
schema.parse("550e8400-e29b-41d4-a716-446655440000"); // ✅
schema.parse("123e4567-e89b-12d3-a456-426614174000"); // ✅
schema.parse("not-a-uuid"); // ❌ ZodError: Invalid uuid
schema.parse("550e8400-e29b-41d4-a716"); // ❌ ZodError: Invalid uuid
.regex()
Signature: ZodString.regex(regex: RegExp, message?: string): ZodString
Validates that string matches the given regular expression.
const schema = z.string().regex(/^[A-Z]{3}-\d{3}$/);
schema.parse("ABC-123"); // ✅ Returns "ABC-123"
schema.parse("XYZ-999"); // ✅ Returns "XYZ-999"
schema.parse("abc-123"); // ❌ ZodError: Invalid
schema.parse("ABCD-12"); // ❌ ZodError: Invalid
// Custom message
z.string().regex(/^\d+$/, "Numbers only").parse("abc"); // ❌ ZodError: Numbers only
.startsWith()
Signature: ZodString.startsWith(prefix: string, message?: string): ZodString
Requires string to start with the given prefix.
const schema = z.string().startsWith("https://");
schema.parse("https://example.com"); // ✅
schema.parse("https://"); // ✅
schema.parse("http://example.com"); // ❌ ZodError: Invalid input: must start with "https://"
.endsWith()
Signature: ZodString.endsWith(suffix: string, message?: string): ZodString
Requires string to end with the given suffix.
const schema = z.string().endsWith(".json");
schema.parse("config.json"); // ✅
schema.parse("data.json"); // ✅
schema.parse("config.yaml"); // ❌ ZodError: Invalid input: must end with ".json"
.trim()
Signature: ZodString.trim(): ZodString
Transforms the string by removing leading and trailing whitespace.
This is a transformation, not a validation.
const schema = z.string().trim();
schema.parse(" hello "); // ✅ Returns "hello"
schema.parse("\n\tworld\n"); // ✅ Returns "world"
schema.parse("no spaces"); // ✅ Returns "no spaces"
// Often combined with .min() to reject empty/whitespace-only
const nonEmpty = z.string().trim().min(1);
nonEmpty.parse(" "); // ❌ ZodError: String must contain at least 1 character(s)
.toLowerCase()
Signature: ZodString.toLowerCase(): ZodString
Transforms the string to lowercase.
const schema = z.string().toLowerCase();
schema.parse("HELLO"); // ✅ Returns "hello"
schema.parse("HeLLo WoRLD"); // ✅ Returns "hello world"
schema.parse("already"); // ✅ Returns "already"
.toUpperCase()
Signature: ZodString.toUpperCase(): ZodString
Transforms the string to uppercase.
const schema = z.string().toUpperCase();
schema.parse("hello"); // ✅ Returns "HELLO"
schema.parse("HeLLo WoRLD"); // ✅ Returns "HELLO WORLD"
schema.parse("ALREADY"); // ✅ Returns "ALREADY"
Chaining Example
String validators can be chained:
const usernameSchema = z.string()
.trim()
.toLowerCase()
.min(3)
.max(20)
.regex(/^[a-z0-9_]+$/);
usernameSchema.parse(" John_Doe123 "); // ✅ Returns "john_doe123"
usernameSchema.parse(" AB "); // ❌ ZodError: min 3
usernameSchema.parse("invalid-name!"); // ❌ ZodError: regex fails
Number Validators
Introduction
All methods below are called on a ZodNumber instance (returned by z.number()).
.min()
Signature: ZodNumber.min(value: number, message?: string | { message?: string }): ZodNumber
Requires number to be greater than or equal to value.
const schema = z.number().min(5);
schema.parse(5); // ✅ Returns 5
schema.parse(10); // ✅ Returns 10
schema.parse(4.99); // ❌ ZodError: Number must be greater than or equal to 5
schema.parse(-10); // ❌ ZodError
.max()
Signature: ZodNumber.max(value: number, message?: string | { message?: string }): ZodNumber
Requires number to be less than or equal to value.
const schema = z.number().max(100);
schema.parse(100); // ✅ Returns 100
schema.parse(50); // ✅ Returns 50
schema.parse(101); // ❌ ZodError: Number must be less than or equal to 100
.int()
Signature: ZodNumber.int(message?: string | { message?: string }): ZodNumber
Requires number to be an integer (no decimal part).
const schema = z.number().int();
schema.parse(42); // ✅ Returns 42
schema.parse(-10); // ✅ Returns -10
schema.parse(0); // ✅ Returns 0
schema.parse(3.14); // ❌ ZodError: Expected integer, received float
schema.parse(5.0); // ✅ Returns 5 (5.0 === 5 in JS)
.positive()
Signature: ZodNumber.positive(message?: string | { message?: string }): ZodNumber
Requires number to be greater than 0.
const schema = z.number().positive();
schema.parse(1); // ✅ Returns 1
schema.parse(0.001); // ✅ Returns 0.001
schema.parse(0); // ❌ ZodError: Number must be greater than 0
schema.parse(-5); // ❌ ZodError
.negative()
Signature: ZodNumber.negative(message?: string | { message?: string }): ZodNumber
Requires number to be less than 0.
const schema = z.number().negative();
schema.parse(-1); // ✅ Returns -1
schema.parse(-0.01); // ✅ Returns -0.01
schema.parse(0); // ❌ ZodError: Number must be less than 0
schema.parse(5); // ❌ ZodError
.nonnegative()
Signature: ZodNumber.nonnegative(message?: string | { message?: string }): ZodNumber
Requires number to be greater than or equal to 0.
const schema = z.number().nonnegative();
schema.parse(0); // ✅ Returns 0
schema.parse(100); // ✅ Returns 100
schema.parse(-1); // ❌ ZodError: Number must be greater than or equal to 0
schema.parse(-0.01); // ❌ ZodError
Note: There's also .nonpositive() (≤ 0).
.finite()
Signature: ZodNumber.finite(message?: string | { message?: string }): ZodNumber
Requires number to be finite (rejects Infinity and -Infinity).
const schema = z.number().finite();
schema.parse(999999999); // ✅ Returns 999999999
schema.parse(-999999999); // ✅ Returns -999999999
schema.parse(0); // ✅ Returns 0
schema.parse(Infinity); // ❌ ZodError: Number must be finite
schema.parse(-Infinity); // ❌ ZodError
.safe()
Signature: ZodNumber.safe(message?: string | { message?: string }): ZodNumber
Restricts the number to JavaScript's "safe integer" range:
-9007199254740991 to 9007199254740991
(Number.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER)
Why it exists: JavaScript's number type loses precision for integers outside this range.
const schema = z.number().safe();
schema.parse(9007199254740991); // ✅ MAX_SAFE_INTEGER
schema.parse(-9007199254740991); // ✅ MIN_SAFE_INTEGER
schema.parse(9007199254740992); // ❌ ZodError: Number must be safe
// Why this matters:
console.log(9007199254740992 === 9007199254740993); // true (precision lost!)
Additional Number Validators
Method
Signature
Description
.gt(n)
ZodNumber.gt(value: number, message?): ZodNumber
Greater than (exclusive)
.gte(n)
ZodNumber.gte(value: number, message?): ZodNumber
Alias for .min()
.lt(n)
ZodNumber.lt(value: number, message?): ZodNumber
Less than (exclusive)
.lte(n)
ZodNumber.lte(value: number, message?): ZodNumber
Alias for .max()
.multipleOf(n)
ZodNumber.multipleOf(value: number, message?): ZodNumber
Must be divisible by n
Chaining Example
const ageSchema = z.number().int().nonnegative().max(120);
ageSchema.parse(25); // ✅ Returns 25
ageSchema.parse(25.5); // ❌ Not an integer
ageSchema.parse(-1); // ❌ Negative
ageSchema.parse(150); // ❌ Exceeds max
Composite Types
Introduction
Composite types allow you to create schemas for complex data structures.
All below are factory functions on the z namespace.
z.object()
Signature: z.object<T extends ZodRawShape>(shape: T): ZodObject<T>
Where ZodRawShape = { [key: string]: ZodTypeAny }
Creates a schema for an object with specified keys and their types.
By default, unknown keys are stripped from output.
const UserSchema = z.object({
name: z.string(),
age: z.number(),
});
UserSchema.parse({ name: "Alice", age: 30 });
// ✅ Returns { name: "Alice", age: 30 }
UserSchema.parse({ name: "Bob", age: 25, extra: "ignored" });
// ✅ Returns { name: "Bob", age: 25 } — extra key stripped
UserSchema.parse({ name: "Charlie" });
// ❌ ZodError: age is required
UserSchema.parse({ name: "Dave", age: "thirty" });
// ❌ ZodError: age expected number, received string
// Type inference
type User = z.infer<typeof UserSchema>;
// { name: string; age: number }
z.array()
Signature: z.array<T extends ZodTypeAny>(schema: T): ZodArray<T>
Creates a schema for an array where each element matches the given schema.
const schema = z.array(z.number());
schema.parse([1, 2, 3]); // ✅ Returns [1, 2, 3]
schema.parse([]); // ✅ Returns []
schema.parse([1, "two", 3]); // ❌ ZodError: Expected number at index 1
schema.parse("not array"); // ❌ ZodError: Expected array
// Array-specific validators
z.array(z.string()).min(1); // At least 1 element
z.array(z.string()).max(5); // At most 5 elements
z.array(z.string()).length(3); // Exactly 3 elements
z.array(z.string()).nonempty(); // Alias for .min(1), also narrows type
z.tuple()
z.tuple<T extends [ZodTypeAny, ...ZodTypeAny[]]>(items: T): ZodTuple<T>
// With rest element
z.tuple<T, R extends ZodTypeAny>(items: T).rest(rest: R): ZodTuple<T, R>
Creates a schema for a fixed-length array with specific types at each index.
const schema = z.tuple([z.string(), z.number(), z.boolean()]);
schema.parse(["hello", 42, true]); // ✅ Returns ["hello", 42, true]
schema.parse(["hi", 1, false]); // ✅
schema.parse(["hello", 42]); // ❌ ZodError: Expected 3 items, received 2
schema.parse([42, "hello", true]); // ❌ ZodError: Type mismatch at index 0
// With rest element
const withRest = z.tuple([z.string(), z.number()]).rest(z.boolean());
withRest.parse(["a", 1]); // ✅
withRest.parse(["a", 1, true, false, true]); // ✅
withRest.parse(["a", 1, "nope"]); // ❌ Rest element must be boolean
z.record()
// Value-only (keys are strings)
z.record<V extends ZodTypeAny>(valueSchema: V): ZodRecord<ZodString, V>
// Key and value
z.record<K extends ZodString | ZodEnum | ZodUnion, V extends ZodTypeAny>(
keySchema: K,
valueSchema: V
): ZodRecord<K, V>
Creates a schema for an object with arbitrary keys.
Like TypeScript's Record<K, V>.
// String keys, number values
const scores = z.record(z.number());
scores.parse({ alice: 100, bob: 85 }); // ✅
scores.parse({}); // ✅
scores.parse({ alice: "high" }); // ❌ Value must be number
// Constrained keys with enum
const StatusMap = z.record(
z.enum(["pending", "active", "done"]),
z.boolean()
);
StatusMap.parse({ pending: true, active: false }); // ✅
StatusMap.parse({ invalid: true }); // ❌ Invalid key
z.map()
Signature: z.map<K extends ZodTypeAny, V extends ZodTypeAny>(keySchema: K, valueSchema: V): ZodMap<K, V>
Creates a schema for JavaScript Map objects.
const schema = z.map(z.string(), z.number());
const validMap = new Map([["a", 1], ["b", 2]]);
const invalidMap = new Map([["a", "one"]]);
schema.parse(validMap); // ✅ Returns Map { "a" => 1, "b" => 2 }
schema.parse(invalidMap); // ❌ ZodError: Value must be number
schema.parse({}); // ❌ ZodError: Expected Map
schema.parse(new Map()); // ✅ Empty map is valid
z.set()
Signature: z.set<T extends ZodTypeAny>(valueSchema: T): ZodSet<T>
Creates a schema for JavaScript Set objects.
const schema = z.set(z.number());
schema.parse(new Set([1, 2, 3])); // ✅ Returns Set { 1, 2, 3 }
schema.parse(new Set()); // ✅ Empty set
schema.parse(new Set([1, "two"])); // ❌ ZodError: Expected number
schema.parse([1, 2, 3]); // ❌ ZodError: Expected Set
// Size constraints
z.set(z.string()).min(1); // At least 1 element
z.set(z.string()).max(10); // At most 10 elements
z.set(z.string()).size(5); // Exactly 5 elements
z.enum()
Signature: z.enum<T extends [string, ...string[]]>(values: T): ZodEnum<T>
Creates a schema for a set of allowed string literals.
Provides autocomplete and type safety.
const Status = z.enum(["pending", "active", "completed"]);
Status.parse("active"); // ✅ Returns "active"
Status.parse("pending"); // ✅
Status.parse("invalid"); // ❌ ZodError: Invalid enum value
Status.parse(123); // ❌ ZodError: Expected string
// Access enum values
Status.options; // ["pending", "active", "completed"]
Status.enum.active; // "active" (for autocomplete)
// Type inference
type Status = z.infer<typeof Status>; // "pending" | "active" | "completed"
z.nativeEnum()
Signature: z.nativeEnum<T extends EnumLike>(enum: T): ZodNativeEnum<T>
Where EnumLike = { [key: string]: string | number }
Creates a schema from an existing TypeScript enum.
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
const schema = z.nativeEnum(Direction);
schema.parse("UP"); // ✅ Returns "UP"
schema.parse(Direction.Up); // ✅ Returns "UP"
schema.parse("DIAGONAL"); // ❌ ZodError: Invalid enum value
// Numeric enums
enum StatusCode {
OK = 200,
NotFound = 404,
}
const codeSchema = z.nativeEnum(StatusCode);
codeSchema.parse(200); // ✅
codeSchema.parse(500); // ❌
z.union()
Signature: z.union<T extends [ZodTypeAny, ZodTypeAny, ...ZodTypeAny[]]>(types: T): ZodUnion<T>
Creates a schema that accepts any of the given types.
Tries each in order.
const schema = z.union([z.string(), z.number()]);
schema.parse("hello"); // ✅ Returns "hello"
schema.parse(42); // ✅ Returns 42
schema.parse(true); // ❌ ZodError: Invalid input
// Shorthand with .or()
const same = z.string().or(z.number());
// Complex union
const Response = z.union([
z.object({ success: z.literal(true), data: z.string() }),
z.object({ success: z.literal(false), error: z.string() }),
]);
z.discriminatedUnion()
z.discriminatedUnion<
Discriminator extends string,
Types extends [ZodObject<any>, ...ZodObject<any>[]]
>(discriminator: Discriminator, types: Types): ZodDiscriminatedUnion<Discriminator, Types>
Optimized union for objects sharing a common discriminator key.
Better performance and error messages than z.union().
const Result = z.discriminatedUnion("status", [
z.object({ status: z.literal("success"), data: z.string() }),
z.object({ status: z.literal("error"), message: z.string() }),
]);
Result.parse({ status: "success", data: "hello" });
// ✅ Returns { status: "success", data: "hello" }
Result.parse({ status: "error", message: "failed" });
// ✅
Result.parse({ status: "error", data: "wrong field" });
// ❌ ZodError: message is required (knows which branch based on "status")
Result.parse({ status: "unknown" });
// ❌ ZodError: Invalid discriminator value
z.intersection()
Signature: z.intersection<L extends ZodTypeAny, R extends ZodTypeAny>(left: L, right: R): ZodIntersection<L, R>
Combines two schemas — value must satisfy both.
Like TypeScript's A & B.
const A = z.object({ a: z.string() });
const B = z.object({ b: z.number() });
const schema = z.intersection(A, B);
schema.parse({ a: "hello", b: 42 }); // ✅ Returns { a: "hello", b: 42 }
schema.parse({ a: "hello" }); // ❌ ZodError: b is required
schema.parse({ b: 42 }); // ❌ ZodError: a is required
// Shorthand with .and()
const same = A.and(B);
Note: For merging objects, .merge() is often preferred over intersection.
z.literal()
Signature: z.literal<T extends Primitive>(value: T): ZodLiteral<T>
Where Primitive = string | number | boolean | bigint | symbol | null | undefined
Creates a schema that matches exactly one specific primitive value.
const schema = z.literal("hello");
schema.parse("hello"); // ✅ Returns "hello"
schema.parse("Hello"); // ❌ ZodError: Expected "hello"
schema.parse("bye"); // ❌ ZodError
// Other literal types
z.literal(42).parse(42); // ✅
z.literal(true).parse(true); // ✅
z.literal(null).parse(null); // ✅
// Commonly used in discriminated unions
const Event = z.discriminatedUnion("type", [
z.object({ type: z.literal("click"), x: z.number(), y: z.number() }),
z.object({ type: z.literal("keydown"), key: z.string() }),
]);
Modifiers
Introduction
Modifiers change how a schema handles optional, nullable, or default values.
All methods below can be called on any ZodType instance.
.optional()
Signature: ZodType<T>.optional(): ZodOptional<ZodType<T>>
Resulting type: T | undefined
Makes the value optional — undefined is accepted.
const schema = z.string().optional();
schema.parse("hello"); // ✅ Returns "hello"
schema.parse(undefined); // ✅ Returns undefined
schema.parse(null); // ❌ ZodError: Expected string, received null
// In objects
const User = z.object({
name: z.string(),
bio: z.string().optional(),
});
User.parse({ name: "Alice" }); // ✅ { name: "Alice" }
User.parse({ name: "Bob", bio: "Developer" }); // ✅ { name: "Bob", bio: "Developer" }
.nullable()
Signature: ZodType<T>.nullable(): ZodNullable<ZodType<T>>
Resulting type: T | null
Allows null as a valid value.
const schema = z.string().nullable();
schema.parse("hello"); // ✅ Returns "hello"
schema.parse(null); // ✅ Returns null
schema.parse(undefined); // ❌ ZodError: Expected string, received undefined
.nullish()
Signature: ZodType<T>.nullish(): ZodOptional<ZodNullable<ZodType<T>>>
Resulting type: T | null | undefined
Allows both null and undefined.
Equivalent to .nullable().optional().
const schema = z.string().nullish();
schema.parse("hello"); // ✅ Returns "hello"
schema.parse(null); // ✅ Returns null
schema.parse(undefined); // ✅ Returns undefined
schema.parse(123); // ❌ ZodError
.default()
ZodType<T>.default(defaultValue: T): ZodDefault<ZodType<T>>
ZodType<T>.default(defaultFn: () => T): ZodDefault<ZodType<T>>
Provides a default value when input is undefined.
Accepts a value or a function (for mutable/dynamic defaults).
const schema = z.string().default("anonymous");
schema.parse("Alice"); // ✅ Returns "Alice"
schema.parse(undefined); // ✅ Returns "anonymous"
schema.parse(null); // ❌ ZodError (null ≠ undefined)
// Function form (avoids shared mutable reference)
const withArray = z.array(z.string()).default(() => []);
withArray.parse(undefined); // ✅ Returns [] (new array each time)
// In objects — missing keys are undefined
const Config = z.object({
port: z.number().default(3000),
host: z.string().default("localhost"),
});
Config.parse({}); // ✅ { port: 3000, host: "localhost" }
.catch()
ZodType<T>.catch(catchValue: T): ZodCatch<ZodType<T>>
ZodType<T>.catch(catchFn: (ctx: { error: ZodError; input: unknown }) => T): ZodCatch<ZodType<T>>
Returns a fallback value when validation fails (instead of throwing).
Unlike .default(), this catches any invalid input, not just undefined.
const schema = z.string().catch("fallback");
schema.parse("hello"); // ✅ Returns "hello"
schema.parse(undefined); // ✅ Returns "fallback"
schema.parse(null); // ✅ Returns "fallback"
schema.parse(123); // ✅ Returns "fallback"
// Function form with context
const smart = z.number().catch((ctx) => {
console.log("Invalid input:", ctx.input);
return 0;
});
smart.parse("bad"); // ✅ Returns 0, logs "Invalid input: bad"
Input
.default("x")
.catch("x")
undefined
"x"
"x"
null
❌ throws
"x"
123
❌ throws
"x"
.readonly()
Signature: ZodType<T>.readonly(): ZodReadonly<ZodType<T>>
Resulting type: Readonly<T> (for objects/arrays)
Marks the output type as readonly in TypeScript.
No runtime effect — purely for type inference.
const schema = z.object({
name: z.string(),
tags: z.array(z.string()),
}).readonly();
type Result = z.infer<typeof schema>;
// {
// readonly name: string;
// readonly tags: readonly string[];
// }
const data = schema.parse({ name: "Test", tags: ["a", "b"] });
data.name = "New"; // ❌ TypeScript error: Cannot assign to 'name'
data.tags.push("c"); // ❌ TypeScript error: Property 'push' does not exist
// Runtime: no enforcement
(data as any).name = "Mutated"; // Works at runtime
Object Utilities
Introduction
All methods below are called on a ZodObject instance.
They allow you to derive new schemas from existing object schemas.
.extend()
Signature: ZodObject<T>.extend<U extends ZodRawShape>(shape: U): ZodObject<T & U>
Adds new fields to an object schema.
Overwrites existing keys if duplicated.
const Base = z.object({ name: z.string() });
const Extended = Base.extend({ age: z.number() });
Extended.parse({ name: "Alice", age: 30 }); // ✅
Extended.parse({ name: "Bob" }); // ❌ age required
// Overwriting keys
const Overwritten = Base.extend({ name: z.number() });
Overwritten.parse({ name: 123 }); // ✅ name is now number
.merge()
Signature: ZodObject<T>.merge<U extends ZodObject>(other: U): ZodObject<T & U["shape"]>
Merges two object schemas.
Similar to .extend(), but takes a ZodObject instead of a raw shape.
const A = z.object({ a: z.string() });
const B = z.object({ b: z.number() });
const Merged = A.merge(B);
Merged.parse({ a: "hello", b: 42 }); // ✅
Merged.parse({ a: "hello" }); // ❌ b required
// Type: { a: string; b: number }
Note: .merge() doesn't preserve modifiers like .strict(). Use .extend() if you need that.
.pick()
Signature: ZodObject<T>.pick<K extends keyof T>(keys: { [k in K]: true }): ZodObject<Pick<T, K>>
Creates a new schema with only the specified keys.
const User = z.object({
id: z.number(),
name: z.string(),
email: z.string(),
password: z.string(),
});
const PublicUser = User.pick({ name: true, email: true });
PublicUser.parse({ name: "Alice", email: "a@b.com" }); // ✅
PublicUser.parse({ name: "Alice", email: "a@b.com", password: "x" });
// ✅ Returns { name, email } — extra keys stripped
.omit()
Signature: ZodObject<T>.omit<K extends keyof T>(keys: { [k in K]: true }): ZodObject<Omit<T, K>>
Creates a new schema without the specified keys.
const User = z.object({
id: z.number(),
name: z.string(),
password: z.string(),
});
const SafeUser = User.omit({ password: true });
SafeUser.parse({ id: 1, name: "Alice" }); // ✅
// Type: { id: number; name: string }
.partial()
ZodObject<T>.partial(): ZodObject<{ [K in keyof T]: ZodOptional<T[K]> }>
ZodObject<T>.partial<K extends keyof T>(keys: { [k in K]: true }): ZodObject<...>
Makes all (or specified) keys optional.
const User = z.object({
name: z.string(),
age: z.number(),
});
const PartialUser = User.partial();
PartialUser.parse({}); // ✅
PartialUser.parse({ name: "Alice" }); // ✅
PartialUser.parse({ age: 30 }); // ✅
// Partial specific keys
const PartialAge = User.partial({ age: true });
PartialAge.parse({ name: "Alice" }); // ✅ age optional
PartialAge.parse({}); // ❌ name required
.deepPartial()
Signature: ZodObject<T>.deepPartial(): ZodObject<DeepPartial<T>>
Recursively makes all nested object keys optional.
const Schema = z.object({
user: z.object({
name: z.string(),
address: z.object({
city: z.string(),
}),
}),
});
const Deep = Schema.deepPartial();
Deep.parse({}); // ✅
Deep.parse({ user: {} }); // ✅
Deep.parse({ user: { address: {} } }); // ✅
.required()
ZodObject<T>.required(): ZodObject<{ [K in keyof T]: ZodNonOptional<T[K]> }>
ZodObject<T>.required<K extends keyof T>(keys: { [k in K]: true }): ZodObject<...>
Makes all (or specified) optional keys required.
Opposite of .partial().
const Base = z.object({
name: z.string().optional(),
age: z.number().optional(),
});
const Required = Base.required();
Required.parse({ name: "Alice", age: 30 }); // ✅
Required.parse({ name: "Alice" }); // ❌ age required
// Specific keys
const NameRequired = Base.required({ name: true });
NameRequired.parse({ name: "Alice" }); // ✅ age still optional
.passthrough()
Signature: ZodObject<T>.passthrough(): ZodObject<T, "passthrough">
Preserves unknown keys in output (default behavior strips them).
const schema = z.object({ name: z.string() }).passthrough();
schema.parse({ name: "Alice", extra: "kept" });
// ✅ Returns { name: "Alice", extra: "kept" }
// Compare to default
z.object({ name: z.string() }).parse({ name: "Alice", extra: "stripped" });
// ✅ Returns { name: "Alice" } — extra removed
.strict()
Signature: ZodObject<T>.strict(): ZodObject<T, "strict">
Fails validation if unknown keys are present.
const schema = z.object({ name: z.string() }).strict();
schema.parse({ name: "Alice" }); // ✅
schema.parse({ name: "Alice", extra: 1 }); // ❌ ZodError: Unrecognized key "extra"
.strip()
Signature: ZodObject<T>.strip(): ZodObject<T, "strip">
Removes unknown keys from output.
This is the default behavior, but useful to reset after .passthrough() or .strict().
const passthrough = z.object({ a: z.string() }).passthrough();
const stripped = passthrough.strip();
stripped.parse({ a: "hi", b: "removed" }); // ✅ { a: "hi" }
.keyof()
Signature: ZodObject<T>.keyof(): ZodEnum<[keyof T, ...Array<keyof T>]>
Returns a ZodEnum of the object's keys.
const User = z.object({
id: z.number(),
name: z.string(),
email: z.string(),
});
const UserKey = User.keyof();
UserKey.parse("id"); // ✅
UserKey.parse("name"); // ✅
UserKey.parse("password"); // ❌ Invalid enum value
// Type: "id" | "name" | "email"
Introduction
All methods below can be called on any ZodType instance.
Refinements add custom validation logic.
Transformations change the output value or type.
.transform()
Signature: ZodType<T>.transform<U>(fn: (value: T, ctx: RefinementCtx) => U): ZodEffects<ZodType<T>, U>
Transforms the validated value into a different value/type.
Runs after validation passes.
const schema = z.string().transform((val) => val.length);
schema.parse("hello"); // ✅ Returns 5 (number)
schema.parse(""); // ✅ Returns 0
// String to Date
const dateSchema = z.string().transform((val) => new Date(val));
dateSchema.parse("2024-01-15"); // ✅ Returns Date object
// Type changes
type Input = z.input<typeof schema>; // string
type Output = z.output<typeof schema>; // number
// Chained transforms
const chain = z.string()
.transform((s) => s.trim())
.transform((s) => s.toUpperCase())
.transform((s) => s.split(""));
chain.parse(" hi "); // ✅ Returns ["H", "I"]
.refine()
ZodType<T>.refine(
check: (value: T) => boolean | Promise<boolean>,
message?: string | { message?: string; path?: (string | number)[]; params?: object }
): ZodEffects<ZodType<T>, T>
Adds custom validation logic.
Returns true to pass, false to fail.
const schema = z.string().refine((val) => val.includes("@"), {
message: "Must contain @",
});
schema.parse("user@example.com"); // ✅
schema.parse("invalid"); // ❌ ZodError: Must contain @
// Multiple refinements
const password = z.string()
.min(8)
.refine((val) => /[A-Z]/.test(val), "Must contain uppercase")
.refine((val) => /[0-9]/.test(val), "Must contain number");
password.parse("weakpass"); // ❌ Must contain uppercase
password.parse("Weakpass"); // ❌ Must contain number
password.parse("Strong1x"); // ✅
// Object-level refinement
const Form = z.object({
password: z.string(),
confirm: z.string(),
}).refine((data) => data.password === data.confirm, {
message: "Passwords don't match",
path: ["confirm"], // Error appears on confirm field
});
.superRefine()
ZodType<T>.superRefine(
fn: (value: T, ctx: RefinementCtx) => void | Promise<void>
): ZodEffects<ZodType<T>, T>
Advanced refinement with full control.
Add multiple errors, custom error codes, or abort early.
const schema = z.string().superRefine((val, ctx) => {
if (val.length < 3) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Too short",
});
}
if (!val.includes("@")) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Must contain @",
});
}
});
schema.parse("ab"); // ❌ Two errors: "Too short" AND "Must contain @"
// Abort early with fatal flag
const abortEarly = z.string().superRefine((val, ctx) => {
if (val.length === 0) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: "Required",
fatal: true, // Stops further refinements
});
return z.NEVER;
}
});
z.preprocess()
Signature: z.preprocess<T extends ZodTypeAny>(fn: (value: unknown) => unknown, schema: T): ZodEffects<T>
Transforms input before validation.
Useful for coercion from unknown input.
// Coerce string to number
const numSchema = z.preprocess(
(val) => (typeof val === "string" ? parseInt(val, 10) : val),
z.number()
);
numSchema.parse("42"); // ✅ Returns 42
numSchema.parse(42); // ✅ Returns 42
numSchema.parse("abc"); // ❌ NaN fails z.number()
// Normalize empty strings to undefined
const optionalString = z.preprocess(
(val) => (val === "" ? undefined : val),
z.string().optional()
);
optionalString.parse(""); // ✅ Returns undefined
optionalString.parse("hello"); // ✅ Returns "hello"
// Parse JSON
const jsonSchema = z.preprocess(
(val) => (typeof val === "string" ? JSON.parse(val) : val),
z.object({ name: z.string() })
);
jsonSchema.parse('{"name":"Alice"}'); // ✅ { name: "Alice" }
Note: Zod also provides z.coerce.* shortcuts: z.coerce.number(), z.coerce.string(), etc.
.pipe()
Signature: ZodType<T>.pipe<U extends ZodTypeAny>(schema: U): ZodPipeline<ZodType<T>, U>
Chains schemas — output of first becomes input of second.
Useful for transform → validate sequences.
// Transform then validate
const schema = z.string()
.transform((val) => val.length)
.pipe(z.number().min(3));
schema.parse("hello"); // ✅ Returns 5
schema.parse("hi"); // ❌ ZodError: Number must be >= 3
// Coerce and validate
const dateSchema = z.coerce.date().pipe(
z.date().min(new Date("2020-01-01"))
);
dateSchema.parse("2024-01-01"); // ✅
dateSchema.parse("2019-01-01"); // ❌ Too early
Comparison Summary
Method
When it runs
Input
Use case
z.preprocess()
Before validation
unknown
Coerce raw input
.refine()
After validation
T
Custom validation
.superRefine()
After validation
T
Multiple errors, complex logic
.transform()
After validation
T
Change value/type
.pipe()
After first schema
Output of first
Chain schemas
Parsing
Introduction
All methods below can be called on any ZodType instance.
These methods validate input and return typed output.
.parse()
Signature: ZodType<T>.parse(data: unknown): T
Validates input and returns typed output.
Throws ZodError on failure.
const schema = z.string();
schema.parse("hello"); // ✅ Returns "hello"
schema.parse(123); // ❌ Throws ZodError
// Catching errors
try {
schema.parse(123);
} catch (e) {
if (e instanceof z.ZodError) {
console.log(e.errors); // [{ code, message, path, ... }]
}
}
.safeParse()
ZodType<T>.safeParse(data: unknown):
| { success: true; data: T }
| { success: false; error: ZodError }
Validates without throwing.
Returns a discriminated union result.
const schema = z.number();
const good = schema.safeParse(42);
// { success: true, data: 42 }
const bad = schema.safeParse("oops");
// { success: false, error: ZodError }
// Usage pattern
const result = schema.safeParse(input);
if (result.success) {
console.log(result.data); // Typed as number
} else {
console.log(result.error.errors);
}
.parseAsync()
Signature: ZodType<T>.parseAsync(data: unknown): Promise<T>
Async version of .parse().
Required when schema uses async refinements or transforms.
const schema = z.string().refine(async (val) => {
const exists = await checkDatabase(val);
return !exists;
}, "Already taken");
await schema.parseAsync("newuser"); // ✅ Returns "newuser"
await schema.parseAsync("existing"); // ❌ Throws ZodError
// Note: calling .parse() on async schema throws
schema.parse("test"); // ❌ Throws: "Async refinement encountered"
.safeParseAsync()
ZodType<T>.safeParseAsync(data: unknown): Promise<
| { success: true; data: T }
| { success: false; error: ZodError }
>
Async version of .safeParse().
Non-throwing async validation.
const schema = z.string().transform(async (val) => {
const user = await fetchUser(val);
return user;
});
const result = await schema.safeParseAsync("user123");
if (result.success) {
console.log(result.data); // User object
} else {
console.log(result.error);
}
When to Use Each
Method
Throws
Async
Use case
.parse()
Yes
No
Simple validation, let errors propagate
.safeParse()
No
No
Handle errors locally, form validation
.parseAsync()
Yes
Yes
Async refinements/transforms
.safeParseAsync()
No
Yes
Async + local error handling
Type Inference
Introduction
These are TypeScript utility types, not runtime methods.
They extract TypeScript types from Zod schemas.
z.infer<>
Signature: type z.infer<T extends ZodType> = T["_output"]
Extracts the output type from a schema.
The most commonly used inference utility.
const UserSchema = z.object({
name: z.string(),
age: z.number(),
});
type User = z.infer<typeof UserSchema>;
// { name: string; age: number }
// With transforms — infers OUTPUT type
const schema = z.string().transform((s) => s.length);
type Result = z.infer<typeof schema>; // number
// With optionals
const Config = z.object({
port: z.number().default(3000),
host: z.string().optional(),
});
type Config = z.infer<typeof Config>;
// { port: number; host?: string | undefined }
z.input<>
Signature: type z.input<T extends ZodType> = T["_input"]
Extracts the input type — what the schema accepts before transforms/defaults.
const schema = z.string().transform((s) => s.length);
type Input = z.input<typeof schema>; // string
type Output = z.output<typeof schema>; // number
// With defaults
const Config = z.object({
port: z.number().default(3000),
});
type ConfigInput = z.input<typeof Config>;
// { port?: number | undefined } — port is optional in input
type ConfigOutput = z.infer<typeof Config>;
// { port: number } — port is guaranteed in output
z.output<>
Signature: type z.output<T extends ZodType> = T["_output"]
Extracts the output type.
Identical to z.infer<>.
const schema = z.string().transform((s) => parseInt(s, 10));
type Output = z.output<typeof schema>; // number
// z.infer and z.output are aliases
type A = z.infer<typeof schema>; // number
type B = z.output<typeof schema>; // number — same
When Input ≠ Output
Schema
z.input<>
z.output<> / z.infer<>
z.string()
string
string
z.string().transform(s => s.length)
string
number
z.number().default(0)
number | undefined
number
z.string().optional()
string | undefined
string | undefined
z.coerce.number()
unknown
number
How Type Inference Works
Zod uses phantom types to store type information:
// Simplified Zod structure
class ZodType<Output, Input = Output> {
_output!: Output; // Phantom property (never assigned at runtime)
_input!: Input;
}
// Type aliases that extract phantom types
type infer<T extends ZodType<any, any>> = T["_output"];
type input<T extends ZodType<any, any>> = T["_input"];
The ! (definite assignment assertion) silences TypeScript's "never assigned" error for phantom properties.