TypeScript-first schema validation library with static type inference. Define schemas, validate data, and get type safety with zero dependencies.
Add custom validation logic and transform parsed values with refine, superRefine, transform, check, preprocess, and custom schema creation.
Adds custom validation logic to a schema with boolean checks.
/**
* Add custom validation to a schema
* @param schema - Base schema to refine
* @param refinement - Validation function returning boolean
* @param params - Optional error message and path
* @returns Schema with additional validation
*/
function refine<T extends ZodTypeAny>(
schema: T,
refinement: RefinementFunction<T>,
params?: RefineParams
): ZodEffects<T>;
type RefinementFunction<T extends ZodTypeAny> = (
data: z.output<T>
) => boolean | Promise<boolean>;
interface RefineParams {
message?: string;
path?: (string | number)[];
}Usage Examples:
import * as z from 'zod';
// Basic refinement
const PositiveNumber = z.number().refine(
(val) => val > 0,
{ message: 'Number must be positive' }
);
// String refinement
const StrongPassword = z.string().refine(
(val) => val.length >= 8 && /[A-Z]/.test(val) && /[0-9]/.test(val),
{ message: 'Password must be at least 8 characters with uppercase and number' }
);
// Object refinement
const DateRange = z.object({
start: z.date(),
end: z.date(),
}).refine(
(data) => data.end > data.start,
{ message: 'End date must be after start date', path: ['end'] }
);
// Multiple refinements
const ComplexString = z.string()
.min(5)
.refine((val) => !val.includes(' '), 'No spaces allowed')
.refine((val) => /^[a-z]+$/.test(val), 'Only lowercase letters allowed');
// Async refinement
const UniqueUsername = z.string().refine(
async (username) => {
const exists = await checkUsernameExists(username);
return !exists;
},
{ message: 'Username already taken' }
);
declare function checkUsernameExists(username: string): Promise<boolean>;Advanced refinement with context for adding multiple issues.
/**
* Add advanced validation with issue context
* @param schema - Base schema to refine
* @param refinement - Validation function with context
* @returns Schema with advanced validation
*/
function superRefine<T extends ZodTypeAny>(
schema: T,
refinement: SuperRefinementFunction<T>
): ZodEffects<T>;
type SuperRefinementFunction<T extends ZodTypeAny> = (
data: z.output<T>,
ctx: RefinementCtx
) => void | Promise<void>;
interface RefinementCtx {
addIssue(issue: IssueData): void;
path: (string | number)[];
}
type IssueData =
| {
code: 'invalid_type';
expected: ZodParsedType;
received: ZodParsedType;
message?: string;
}
| {
code: 'too_big';
maximum: number | bigint;
inclusive: boolean;
type: 'array' | 'string' | 'number' | 'bigint' | 'set' | 'date';
message?: string;
}
| {
code: 'too_small';
minimum: number | bigint;
inclusive: boolean;
type: 'array' | 'string' | 'number' | 'bigint' | 'set' | 'date';
message?: string;
}
| {
code: 'invalid_format';
format: string;
message?: string;
}
| {
code: 'custom';
message?: string;
params?: Record<string, any>;
};Usage Examples:
import * as z from 'zod';
// Add multiple custom issues
const PasswordSchema = z.string().superRefine((password, ctx) => {
if (password.length < 8) {
ctx.addIssue({
code: 'too_small',
minimum: 8,
type: 'string',
inclusive: true,
message: 'Password must be at least 8 characters',
});
}
if (!/[A-Z]/.test(password)) {
ctx.addIssue({
code: 'custom',
message: 'Password must contain uppercase letter',
});
}
if (!/[0-9]/.test(password)) {
ctx.addIssue({
code: 'custom',
message: 'Password must contain number',
});
}
});
// Object validation with path-specific errors
const FormSchema = z.object({
email: z.string(),
confirmEmail: z.string(),
password: z.string(),
confirmPassword: z.string(),
}).superRefine((data, ctx) => {
if (data.email !== data.confirmEmail) {
ctx.addIssue({
code: 'custom',
message: 'Emails do not match',
path: ['confirmEmail'],
});
}
if (data.password !== data.confirmPassword) {
ctx.addIssue({
code: 'custom',
message: 'Passwords do not match',
path: ['confirmPassword'],
});
}
});
// Async super refinement
const SignupSchema = z.object({
username: z.string(),
email: z.string().email(),
}).superRefine(async (data, ctx) => {
const usernameExists = await checkUsername(data.username);
if (usernameExists) {
ctx.addIssue({
code: 'custom',
message: 'Username already taken',
path: ['username'],
});
}
const emailExists = await checkEmail(data.email);
if (emailExists) {
ctx.addIssue({
code: 'custom',
message: 'Email already registered',
path: ['email'],
});
}
});
declare function checkUsername(username: string): Promise<boolean>;
declare function checkEmail(email: string): Promise<boolean>;Transforms the parsed value to a different type or value.
/**
* Transform the parsed value
* @param schema - Base schema
* @param transform - Transformation function
* @returns Schema with transformation applied
*/
function transform<T extends ZodTypeAny, Output>(
schema: T,
transform: TransformFunction<T, Output>
): ZodEffects<T, Output>;
type TransformFunction<T extends ZodTypeAny, Output> = (
data: z.output<T>
) => Output | Promise<Output>;Usage Examples:
import * as z from 'zod';
// Basic transformation
const StringToNumber = z.string().transform((val) => parseInt(val, 10));
type StringToNumber = z.infer<typeof StringToNumber>; // number
StringToNumber.parse('42'); // => 42
// String manipulation
const TrimmedString = z.string().transform((val) => val.trim());
const UppercaseString = z.string().transform((val) => val.toUpperCase());
// Object transformation
const UserInput = z.object({
firstName: z.string(),
lastName: z.string(),
birthYear: z.string(),
}).transform((data) => ({
fullName: `${data.firstName} ${data.lastName}`,
age: new Date().getFullYear() - parseInt(data.birthYear, 10),
}));
type User = z.infer<typeof UserInput>;
// { fullName: string; age: number }
// Date parsing
const DateFromString = z.string().transform((str) => new Date(str));
const TimestampFromDate = z.date().transform((date) => date.getTime());
// Array transformation
const CommaSeparated = z.string().transform((str) => str.split(','));
type CommaSeparated = z.infer<typeof CommaSeparated>; // string[]
// Chaining transforms
const ProcessedString = z.string()
.transform((s) => s.trim())
.transform((s) => s.toLowerCase())
.transform((s) => s.split(' '));
// Async transformation
const EnrichedUser = z.object({
userId: z.string(),
}).transform(async (data) => {
const userData = await fetchUserData(data.userId);
return { ...data, ...userData };
});
declare function fetchUserData(userId: string): Promise<{ name: string; email: string }>;Preprocesses input before validation (transforms input, not output).
/**
* Preprocess input before validation
* @param preprocessor - Function to preprocess input
* @param schema - Schema to validate preprocessed input
* @returns Schema with preprocessing
*/
function preprocess<T extends ZodTypeAny>(
preprocessor: (arg: unknown) => unknown,
schema: T
): ZodEffects<ZodUnknown, z.output<T>>;Usage Examples:
import * as z from 'zod';
// Trim before validation
const TrimmedString = z.preprocess(
(val) => typeof val === 'string' ? val.trim() : val,
z.string().min(1)
);
TrimmedString.parse(' hello '); // => "hello"
// Parse string to number
const NumberFromString = z.preprocess(
(val) => typeof val === 'string' ? parseFloat(val) : val,
z.number()
);
NumberFromString.parse('42.5'); // => 42.5
// Convert empty string to undefined
const OptionalField = z.preprocess(
(val) => val === '' ? undefined : val,
z.string().optional()
);
// Date parsing
const DateField = z.preprocess(
(arg) => {
if (typeof arg === 'string' || arg instanceof Date) return new Date(arg);
return arg;
},
z.date()
);
// Array coercion
const ArrayField = z.preprocess(
(val) => Array.isArray(val) ? val : [val],
z.array(z.string())
);
ArrayField.parse('single'); // => ["single"]
ArrayField.parse(['multiple', 'items']); // => ["multiple", "items"]
// JSON parsing
const JsonField = z.preprocess(
(val) => {
if (typeof val === 'string') {
try {
return JSON.parse(val);
} catch {
return val;
}
}
return val;
},
z.object({ name: z.string() })
);Creates a custom schema with user-defined validation logic.
/**
* Create a custom schema with validation function
* @param validator - Custom validation function
* @param params - Optional configuration
* @returns Custom schema
*/
function custom<T = any>(
validator: CustomValidator<T>,
params?: CustomParams
): ZodCustom<T>;
type CustomValidator<T> = (data: unknown) => data is T;
interface CustomParams {
errorMap?: ErrorMapFunction;
invalid_type_error?: string;
required_error?: string;
description?: string;
}Usage Examples:
import * as z from 'zod';
// Custom type guard
interface Point {
x: number;
y: number;
}
const PointSchema = z.custom<Point>(
(data): data is Point => {
return (
typeof data === 'object' &&
data !== null &&
'x' in data &&
'y' in data &&
typeof (data as any).x === 'number' &&
typeof (data as any).y === 'number'
);
},
{ invalid_type_error: 'Invalid point object' }
);
PointSchema.parse({ x: 10, y: 20 }); // => { x: 10, y: 20 }
// Custom validation for File
const ImageFile = z.custom<File>(
(data): data is File => {
return data instanceof File && data.type.startsWith('image/');
},
{ invalid_type_error: 'Must be an image file' }
);
// Custom branded type
type Email = string & { __brand: 'Email' };
const EmailSchema = z.custom<Email>(
(data): data is Email => {
return typeof data === 'string' && /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data);
}
);
// Custom with complex validation
interface CreditCard {
number: string;
cvv: string;
expiry: string;
}
const CreditCardSchema = z.custom<CreditCard>(
(data): data is CreditCard => {
if (typeof data !== 'object' || data === null) return false;
const card = data as any;
return (
typeof card.number === 'string' &&
/^\d{16}$/.test(card.number) &&
typeof card.cvv === 'string' &&
/^\d{3,4}$/.test(card.cvv) &&
typeof card.expiry === 'string' &&
/^\d{2}\/\d{2}$/.test(card.expiry)
);
}
);Validates that a value is an instance of a specific class.
/**
* Create schema that validates instanceof check
* @param cls - Class constructor to check against
* @param params - Optional configuration
* @returns Custom schema for instanceof validation
*/
function instanceof<T extends typeof Class>(
cls: T,
params?: ZodInstanceOfParams
): ZodCustom<InstanceType<T>, InstanceType<T>>;
interface ZodInstanceOfParams {
errorMap?: ErrorMapFunction;
invalid_type_error?: string;
required_error?: string;
description?: string;
}
declare const Class: abstract new (...args: any[]) => any;Usage Examples:
import * as z from 'zod';
// Validate Date instances
const DateSchema = z.instanceof(Date);
DateSchema.parse(new Date()); // => Date object
DateSchema.parse('2024-01-01'); // => ZodError: Expected Date instance
// Validate custom class instances
class User {
constructor(public name: string) {}
}
const UserSchema = z.instanceof(User);
UserSchema.parse(new User('Alice')); // => User { name: 'Alice' }
UserSchema.parse({ name: 'Bob' }); // => ZodError: Expected User instance
// With error message
const ErrorSchema = z.instanceof(Error, {
invalid_type_error: 'Must be an Error instance'
});
// Built-in classes
const MapSchema = z.instanceof(Map);
const SetSchema = z.instanceof(Set);
const RegExpSchema = z.instanceof(RegExp);Creates a validation check that can be added to schemas.
/**
* Create a validation check
* @param fn - Check function returning boolean
* @param message - Optional error message
* @returns Check function
*/
function check<T>(
fn: (data: T) => boolean,
message?: string
): (data: T) => boolean;Usage Examples:
import * as z from 'zod';
// Create reusable checks
const isEven = z.check((n: number) => n % 2 === 0, 'Must be even');
const isPositive = z.check((n: number) => n > 0, 'Must be positive');
// Apply checks using refine
const EvenPositiveNumber = z.number()
.refine(isPositive)
.refine(isEven);
// String checks
const noSpaces = z.check((s: string) => !s.includes(' '), 'No spaces allowed');
const alphanumeric = z.check(
(s: string) => /^[a-zA-Z0-9]+$/.test(s),
'Only alphanumeric characters'
);
const Username = z.string()
.min(3)
.refine(noSpaces)
.refine(alphanumeric);Refinements and transformations can be combined:
import * as z from 'zod';
// Validate then transform
const ProcessedAge = z.number()
.int()
.min(0)
.max(120)
.refine((age) => age >= 18, 'Must be 18 or older')
.transform((age) => ({ age, isAdult: true }));
// Transform then validate
const ParsedDate = z.string()
.transform((str) => new Date(str))
.refine((date) => !isNaN(date.getTime()), 'Invalid date');
// Multiple steps
const ComplexValidation = z.string()
.min(3)
.transform((s) => s.trim().toLowerCase())
.refine((s) => s.length >= 3, 'Still too short after trimming')
.transform((s) => ({ username: s, normalized: true }));
// Async chain
const AsyncChain = z.string()
.refine(async (val) => await checkFormat(val), 'Invalid format')
.transform(async (val) => await enrichData(val))
.refine(async (data) => await validateEnriched(data), 'Validation failed');
declare function checkFormat(val: string): Promise<boolean>;
declare function enrichData(val: string): Promise<{ data: string }>;
declare function validateEnriched(data: { data: string }): Promise<boolean>;type ZodTypeAny = ZodType<any, any, any>;
class ZodEffects<Input extends ZodTypeAny, Output = Input['_output']> {
readonly _type: 'ZodEffects';
readonly innerType: Input;
}
type RefinementFunction<T extends ZodTypeAny> = (
data: z.output<T>
) => boolean | Promise<boolean>;
type SuperRefinementFunction<T extends ZodTypeAny> = (
data: z.output<T>,
ctx: RefinementCtx
) => void | Promise<void>;
type TransformFunction<T extends ZodTypeAny, Output> = (
data: z.output<T>
) => Output | Promise<Output>;
type CustomValidator<T> = (data: unknown) => data is T;
interface RefineParams {
message?: string;
path?: (string | number)[];
}
interface CustomParams {
errorMap?: ErrorMapFunction;
invalid_type_error?: string;
required_error?: string;
description?: string;
}
interface RefinementCtx {
addIssue(issue: IssueData): void;
path: (string | number)[];
}
type ErrorMapFunction = (
issue: ZodIssueOptionalMessage,
ctx: ErrorMapCtx
) => { message: string };
type ZodParsedType =
| 'string'
| 'number'
| 'bigint'
| 'boolean'
| 'symbol'
| 'undefined'
| 'null'
| 'array'
| 'object'
| 'function'
| 'date'
| 'map'
| 'set'
| 'promise'
| 'unknown'
| 'never'
| 'void'
| 'nan';Install with Tessl CLI
npx tessl i tessl/npm-zoddocs
guides
reference