This guide walks you through the core concepts of Zod: defining schemas, parsing data, type inference, and error handling.
Schema Definition
A schema defines the shape and validation rules for your data. Start by defining a simple object schema:
import * as z from "zod" ;
const Player = z . object ({
username: z . string (),
xp: z . number (),
});
You can build more complex schemas by composing primitive types:
const User = z . object ({
name: z . string (),
age: z . number (),
email: z . optional ( z . string ()),
isAdmin: z . boolean (),
});
Schemas are immutable. Methods like .check() and .optional() return new schema instances.
Parsing Data
Use .parse() for validation
The .parse() method validates input and returns a deep clone if valid. It throws a ZodError if validation fails: const Player = z . object ({
username: z . string (),
xp: z . number (),
});
// Valid input
const result = Player . parse ({ username: "billie" , xp: 100 });
console . log ( result );
// => { username: "billie", xp: 100 }
Handle validation errors
When parsing fails, Zod throws a detailed error: try {
Player . parse ({ username: 42 , xp: "100" });
} catch ( err ) {
if ( err instanceof z . ZodError ) {
console . log ( err . issues );
/* [
{
expected: 'string',
code: 'invalid_type',
path: [ 'username' ],
message: 'Invalid input: expected string'
},
{
expected: 'number',
code: 'invalid_type',
path: [ 'xp' ],
message: 'Invalid input: expected number'
}
] */
}
}
Use .safeParse() to avoid exceptions
For error handling without try/catch, use .safeParse(). It returns a discriminated union: const result = Player . safeParse ({ username: 42 , xp: "100" });
if ( ! result . success ) {
// result.error is a ZodError instance
console . log ( result . error . issues );
} else {
// result.data has the correct type: { username: string; xp: number }
console . log ( result . data );
}
Type Inference
Zod automatically infers TypeScript types from your schemas using z.infer<> or z.output<>:
const Player = z . object ({
username: z . string (),
xp: z . number (),
});
// Extract the TypeScript type
type Player = z . infer < typeof Player >;
// type Player = {
// username: string;
// xp: number;
// }
// Use the inferred type
const player : Player = { username: "billie" , xp: 100 };
For schemas with transformations, use z.input<> to get the input type and z.output<> (or z.infer<>) to get the output type.
When you use transforms, input and output types can differ:
const StringToNumber = z . string (). check (
z . refine (( val ) => ! isNaN ( Number ( val )))
);
type Input = z . input < typeof StringToNumber >; // string
type Output = z . output < typeof StringToNumber >; // string
Error Handling
Zod provides detailed error information when validation fails.
Working with ZodError
Every failed parse produces a ZodError with an issues array:
const result = z . string (). safeParse ( 123 );
if ( ! result . success ) {
console . log ( result . error . issues );
// [{
// code: 'invalid_type',
// expected: 'string',
// received: 'number',
// path: [],
// message: 'Invalid input'
// }]
}
Custom Error Messages
You can provide custom error messages when defining schemas:
const Email = z . email ( "Please provide a valid email address" );
const result = Email . safeParse ( "invalid-email" );
if ( ! result . success ) {
console . log ( result . error . issues [ 0 ]. message );
// => "Please provide a valid email address"
}
Or use a function for dynamic messages:
const Email = z . email ({
error : () => "Please provide a valid email address"
});
Complete Example
Here’s a complete example combining all concepts:
import * as z from "zod" ;
// Define the schema
const UserSchema = z . object ({
name: z . string (),
age: z . number (),
email: z . optional ( z . email ( "Invalid email format" )),
});
// Infer the TypeScript type
type User = z . infer < typeof UserSchema >;
// Parse some data
const input = {
name: "John Doe" ,
age: 30 ,
email: "[email protected] " ,
};
const result = UserSchema . safeParse ( input );
if ( result . success ) {
// Type-safe access to validated data
const user : User = result . data ;
console . log ( `Welcome, ${ user . name } !` );
} else {
// Handle validation errors
console . error ( "Validation failed:" );
result . error . issues . forEach (( issue ) => {
console . error ( `- ${ issue . path . join ( '.' ) } : ${ issue . message } ` );
});
}
Next Steps
Primitives Explore all primitive types: strings, numbers, dates, and more
Objects Learn about object schemas, nesting, and composition
Arrays & Tuples Work with arrays, tuples, and collections
Advanced Types Master unions, intersections, and discriminated unions