TypeScript-first schema validation library with static type inference. Define schemas, validate data, and get type safety with zero dependencies.
Transform schemas by making them optional, nullable, with defaults, or applying other modifications to change validation behavior and type signatures.
Makes a schema accept undefined values in addition to its base type.
/**
* Create an optional schema that accepts undefined
* @param schema - Base schema to make optional
* @returns Optional schema accepting base type or undefined
*/
function optional<T extends ZodTypeAny>(schema: T): ZodOptional<T>;Usage Examples:
import * as z from 'zod';
const OptionalString = z.optional(z.string());
type OptionalString = z.infer<typeof OptionalString>; // string | undefined
OptionalString.parse('hello'); // => "hello"
OptionalString.parse(undefined); // => undefined
// Method syntax
const SameAsAbove = z.string().optional();
// In object schemas
const User = z.object({
name: z.string(),
nickname: z.string().optional(),
age: z.number().optional(),
});
type User = z.infer<typeof User>;
// { name: string; nickname?: string | undefined; age?: number | undefined }Makes a schema optional but excludes undefined from the type (only affects presence, not type).
/**
* Create an exact optional schema (affects presence not type)
* @param schema - Base schema to make exactly optional
* @returns Exact optional schema
*/
function exactOptional<T extends ZodTypeAny>(schema: T): ZodOptional<T>;Usage Examples:
import * as z from 'zod';
const ExactOptionalString = z.exactOptional(z.string());
// Property can be omitted but not explicitly undefined
const Schema = z.object({
name: z.string(),
nickname: z.string().exactOptional(),
});
Schema.parse({ name: 'Alice' }); // => valid (nickname omitted)
Schema.parse({ name: 'Alice', nickname: 'Ali' }); // => valid
// Schema.parse({ name: 'Alice', nickname: undefined }); // may throw based on configMakes a schema accept null values in addition to its base type.
/**
* Create a nullable schema that accepts null
* @param schema - Base schema to make nullable
* @returns Nullable schema accepting base type or null
*/
function nullable<T extends ZodTypeAny>(schema: T): ZodNullable<T>;Usage Examples:
import * as z from 'zod';
const NullableString = z.nullable(z.string());
type NullableString = z.infer<typeof NullableString>; // string | null
NullableString.parse('hello'); // => "hello"
NullableString.parse(null); // => null
// Method syntax
const SameAsAbove = z.string().nullable();
// In object schemas
const User = z.object({
name: z.string(),
middleName: z.string().nullable(),
deletedAt: z.date().nullable(),
});
type User = z.infer<typeof User>;
// { name: string; middleName: string | null; deletedAt: Date | null }Makes a schema accept both null and undefined values (combination of optional and nullable).
/**
* Create a nullish schema that accepts null or undefined
* @param schema - Base schema to make nullish
* @returns Nullish schema accepting base type, null, or undefined
*/
function nullish<T extends ZodTypeAny>(schema: T): ZodNullish<T>;Usage Examples:
import * as z from 'zod';
const NullishString = z.nullish(z.string());
type NullishString = z.infer<typeof NullishString>; // string | null | undefined
NullishString.parse('hello'); // => "hello"
NullishString.parse(null); // => null
NullishString.parse(undefined); // => undefined
// Method syntax
const SameAsAbove = z.string().nullish();
// Common pattern for optional database fields
const DatabaseRecord = z.object({
id: z.string(),
name: z.string(),
description: z.string().nullish(), // can be missing, null, or string
});Provides a default value when the input is undefined.
/**
* Create a schema with a default value
* @param schema - Base schema
* @param defaultValue - Default value or function returning default value
* @returns Schema with default value for undefined inputs
*/
function _default<T extends ZodTypeAny>(
schema: T,
defaultValue: DefaultValue<T>
): ZodDefault<T>;
type DefaultValue<T extends ZodTypeAny> =
| z.output<T>
| (() => z.output<T>);Usage Examples:
import * as z from 'zod';
const StringWithDefault = z.string().default('default value');
type StringWithDefault = z.infer<typeof StringWithDefault>; // string
StringWithDefault.parse(undefined); // => "default value"
StringWithDefault.parse('custom'); // => "custom"
// Function defaults (computed each time)
const TimestampedSchema = z.object({
message: z.string(),
timestamp: z.date().default(() => new Date()),
});
// Array with default
const TagsSchema = z.array(z.string()).default([]);
// Number with default
const CounterSchema = z.number().default(0);
// Complex defaults
const ConfigSchema = z.object({
host: z.string().default('localhost'),
port: z.number().default(3000),
ssl: z.boolean().default(false),
options: z.object({
timeout: z.number(),
}).default({ timeout: 5000 }),
});
// Note: default makes the input optional
const Schema = z.object({
name: z.string(),
role: z.string().default('user'),
});
Schema.parse({ name: 'Alice' }); // => { name: "Alice", role: "user" }Provides a default value for the input (before parsing), different from default which applies to output.
/**
* Create a schema with a prefault (input default) value
* @param schema - Base schema
* @param prefaultValue - Prefault value or function returning prefault value
* @returns Schema with prefault value for undefined inputs
*/
function prefault<T extends ZodTypeAny>(
schema: T,
prefaultValue: PrefaultValue<T>
): ZodPrefault<T>;
type PrefaultValue<T extends ZodTypeAny> =
| z.input<T>
| (() => z.input<T>);Usage Examples:
import * as z from 'zod';
// Prefault applies to input before transformation
const Schema = z
.string()
.transform((val) => val.toUpperCase())
.prefault('hello');
Schema.parse(undefined); // => "HELLO" (prefault value transformed)
Schema.parse('world'); // => "WORLD"
// Difference between default and prefault
const WithDefault = z.string().transform((s) => s.length).default(0);
// default applies after transform: undefined -> 0 (no transformation)
const WithPrefault = z.string().transform((s) => s.length).prefault('');
// prefault applies before transform: undefined -> '' -> 0 (transformed)Catches validation errors and returns a fallback value instead of throwing.
/**
* Create a schema that catches errors and returns a fallback value
* @param schema - Base schema
* @param catchValue - Fallback value or function receiving error
* @returns Schema that never throws, returns fallback on error
*/
function catch_<T extends ZodTypeAny>(
schema: T,
catchValue: CatchValue<T>
): ZodCatch<T>;
type CatchValue<T extends ZodTypeAny> =
| z.output<T>
| ((error: ZodError) => z.output<T>);Usage Examples:
import * as z from 'zod';
const SafeNumber = z.number().catch(0);
SafeNumber.parse(42); // => 42
SafeNumber.parse('invalid'); // => 0 (caught error)
// Function catch with error inspection
const SafeString = z.string().catch((error) => {
console.error('Parse error:', error.message);
return 'fallback';
});
// Useful for config parsing
const Config = z.object({
port: z.number().catch(3000),
host: z.string().catch('localhost'),
debug: z.boolean().catch(false),
});
// Parsing bad config returns defaults
Config.parse({ port: 'invalid', host: 123 });
// => { port: 3000, host: "localhost", debug: false }
// Array with catch
const SafeArray = z.array(z.number()).catch([]);
SafeArray.parse('not an array'); // => []Makes a schema readonly (TypeScript type-level only, no runtime effect).
/**
* Create a readonly schema (type-level only)
* @param schema - Base schema to make readonly
* @returns Readonly schema
*/
function readonly<T extends ZodTypeAny>(schema: T): ZodReadonly<T>;Usage Examples:
import * as z from 'zod';
const ReadonlyArray = z.readonly(z.array(z.string()));
type ReadonlyArray = z.infer<typeof ReadonlyArray>; // readonly string[]
const ReadonlyObject = z.object({
name: z.string(),
age: z.number(),
}).readonly();
type ReadonlyObject = z.infer<typeof ReadonlyObject>;
// { readonly name: string; readonly age: number }
// Useful for immutable data structures
const ImmutableUser = z.readonly(z.object({
id: z.string(),
name: z.string(),
createdAt: z.date(),
}));Converts a schema to return boolean success instead of throwing errors.
/**
* Create a schema that returns boolean success instead of throwing
* @param schema - Base schema
* @returns Success schema returning boolean
*/
function success<T extends ZodTypeAny>(schema: T): ZodSuccess<T>;Usage Examples:
import * as z from 'zod';
const IsValidEmail = z.success(z.string().email());
IsValidEmail.parse('valid@example.com'); // => true
IsValidEmail.parse('invalid'); // => false (doesn't throw)
// Useful for validation predicates
const isPositive = z.success(z.number().positive());
console.log(isPositive.parse(5)); // => true
console.log(isPositive.parse(-5)); // => falseRemoves optional modifier from a schema, making it required.
/**
* Remove optional modifier from a schema
* @param schema - Optional schema to make required
* @returns Required schema without optional
*/
function nonoptional<T extends ZodTypeAny>(schema: T): ZodNonOptional<T>;Usage Examples:
import * as z from 'zod';
const OptionalString = z.string().optional();
const RequiredString = z.nonoptional(OptionalString);
type OptionalString = z.infer<typeof OptionalString>; // string | undefined
type RequiredString = z.infer<typeof RequiredString>; // string
// Useful for object schema manipulation
const PartialUser = z.object({
name: z.string().optional(),
email: z.string().optional(),
});
const RequiredUser = PartialUser.required(); // Makes all fields requiredAdds a nominal type brand for creating distinct types with the same runtime representation.
/**
* Add a nominal type brand to a schema
* @param schema - Base schema to brand
* @returns Branded schema with distinct type
*/
interface ZodType<Output, Input = Output> {
brand<Brand extends string | symbol>(): ZodBranded<this, Brand>;
}
const $brand: unique symbol;Usage Examples:
import * as z from 'zod';
// Create branded types
const UserId = z.string().brand<'UserId'>();
const OrderId = z.string().brand<'OrderId'>();
type UserId = z.infer<typeof UserId>; // string & { [z.$brand]: "UserId" }
type OrderId = z.infer<typeof OrderId>; // string & { [z.$brand]: "OrderId" }
// Runtime they're both strings, but TypeScript treats them as different types
const userId: UserId = UserId.parse('user-123');
const orderId: OrderId = OrderId.parse('order-456');
// Type error: can't assign one to the other
// const x: UserId = orderId; // TypeScript error
// Useful for domain modeling
const Email = z.string().email().brand<'Email'>();
const URL = z.string().url().brand<'URL'>();
const UUID = z.string().uuid().brand<'UUID'>();
type Email = z.infer<typeof Email>;
type URL = z.infer<typeof URL>;
type UUID = z.infer<typeof UUID>;
// Function only accepts branded types
function sendEmail(to: Email, subject: string) {
// Implementation
}
const email = Email.parse('user@example.com');
sendEmail(email, 'Hello'); // OK
// sendEmail('user@example.com', 'Hello'); // TypeScript errorAdds a description to a schema for documentation purposes.
/**
* Add a description to a schema
* @param description - Description string
* @returns Schema with description metadata
*/
interface ZodType<Output, Input = Output> {
describe(description: string): this;
}Usage Examples:
import * as z from 'zod';
const Username = z.string()
.min(3)
.max(20)
.describe('User account username (3-20 characters)');
const User = z.object({
username: Username,
email: z.string().email().describe('User email address'),
age: z.number().int().positive().describe('User age in years'),
});
// Description is available in schema metadata
console.log(Username.description); // "User account username (3-20 characters)"Adds custom metadata to a schema for application-specific purposes.
/**
* Add custom metadata to a schema
* @param metadata - Custom metadata object
* @returns Schema with custom metadata
*/
interface ZodType<Output, Input = Output> {
meta(metadata: any): this;
}Usage Examples:
import * as z from 'zod';
const Username = z.string().meta({
label: 'Username',
helpText: 'Choose a unique username',
placeholder: 'john_doe',
});
const FormSchema = z.object({
name: z.string().meta({ fieldType: 'text', required: true }),
email: z.string().email().meta({ fieldType: 'email', required: true }),
age: z.number().meta({ fieldType: 'number', min: 0, max: 120 }),
});
// Access metadata
console.log(Username._def.meta);Modifiers can be chained together:
import * as z from 'zod';
const Schema = z.string()
.min(3)
.optional()
.default('default')
.describe('A string field')
.meta({ custom: 'data' });
const ComplexField = z.number()
.positive()
.nullable()
.default(0)
.brand<'Score'>()
.describe('User score');
const CatchWithDefault = z.number()
.catch(0)
.default(100);type ZodTypeAny = ZodType<any, any, any>;
class ZodOptional<T extends ZodTypeAny> {
readonly _type: 'ZodOptional';
unwrap(): T;
}
class ZodNullable<T extends ZodTypeAny> {
readonly _type: 'ZodNullable';
unwrap(): T;
}
class ZodNullish<T extends ZodTypeAny> {
readonly _type: 'ZodNullish';
unwrap(): T;
}
class ZodDefault<T extends ZodTypeAny> {
readonly _type: 'ZodDefault';
readonly _defaultValue: () => z.output<T>;
removeDefault(): T;
}
class ZodPrefault<T extends ZodTypeAny> {
readonly _type: 'ZodPrefault';
readonly _prefaultValue: () => z.input<T>;
}
class ZodCatch<T extends ZodTypeAny> {
readonly _type: 'ZodCatch';
readonly _catchValue: (error: ZodError) => z.output<T>;
removeCatch(): T;
}
class ZodReadonly<T extends ZodTypeAny> {
readonly _type: 'ZodReadonly';
unwrap(): T;
}
class ZodSuccess<T extends ZodTypeAny> {
readonly _type: 'ZodSuccess';
}
class ZodNonOptional<T extends ZodTypeAny> {
readonly _type: 'ZodNonOptional';
}
class ZodBranded<T extends ZodTypeAny, Brand extends string | symbol> {
readonly _type: 'ZodBranded';
readonly _brand: Brand;
unwrap(): T;
}
type DefaultValue<T extends ZodTypeAny> = z.output<T> | (() => z.output<T>);
type PrefaultValue<T extends ZodTypeAny> = z.input<T> | (() => z.input<T>);
type CatchValue<T extends ZodTypeAny> = z.output<T> | ((error: ZodError) => z.output<T>);
class ZodError<T = any> extends Error {
issues: ZodIssue[];
}Install with Tessl CLI
npx tessl i tessl/npm-zoddocs
guides
reference