Usage
Basic Usage
Zod provides a simple API to define and validate data schemas. This section covers the fundamental usage of Zod, including schema creation, parsing, and type inference.
Creating a Simple String Schema
Import z
from Zod:
import { z } from "zod";
Define a basic string schema:
const mySchema = z.string();
Parsing Values
You can validate data using the .parse()
method:
mySchema.parse("tuna"); // => "tuna"
mySchema.parse(12); // Throws ZodError
If the input is invalid, parse
throws an error. To handle errors gracefully, use .safeParse()
:
mySchema.safeParse("tuna");
// => { success: true, data: "tuna" }
mySchema.safeParse(12);
/* =>
{
success: false,
error: ZodError
}
*/
Defining Object Schemas
Zod allows you to define schemas for structured objects.
const User = z.object({
username: z.string(),
age: z.number()
});
Parsing an Object
User.parse({ username: "Alice", age: 25 }); // Valid ✅
User.parse({ username: "Alice", age: "25" }); // Throws ZodError ❌
Inferring TypeScript Types
Zod schemas can infer TypeScript types automatically:
type User = z.infer<typeof User>;
/* Equivalent to:
type User = {
username: string;
age: number;
}
*/
Primitive Types in Zod
Zod provides built-in validation for JavaScript primitive types.
z.string(); // Validates a string
z.number(); // Validates a number
z.bigint(); // Validates a bigint
z.boolean(); // Validates a boolean
z.date(); // Validates a Date object
z.symbol(); // Validates a symbol
Handling Special Types
Empty Types
Zod has built-in support for values that represent emptiness.
z.undefined(); // Matches undefined
z.null(); // Matches null
z.void(); // Matches undefined (useful for function return types)
Catch-All Types
These schemas accept any value.
z.any(); // Accepts any value
z.unknown(); // Similar to `any` but forces type checking in TypeScript
Never Type
A never
schema ensures that no value is valid.
z.never(); // Always fails validation
Advanced Schema Composition
Zod enables you to create more complex schemas by combining basic ones.
Array Schema
Validate an array of a specific type.
const NumberArray = z.array(z.number());
NumberArray.parse([1, 2, 3]); // ✅ Valid
NumberArray.parse(["one", 2, 3]); // ❌ Throws ZodError
Tuple Schema
Fixed-length arrays with specific types for each element.
const myTuple = z.tuple([z.string(), z.number()]);
myTuple.parse(["hello", 42]); // ✅ Valid
myTuple.parse([42, "hello"]); // ❌ Throws ZodError
Union Types
Allow multiple possible data types.
const StringOrNumber = z.union([z.string(), z.number()]);
StringOrNumber.parse("hello"); // ✅ Valid
StringOrNumber.parse(123); // ✅ Valid
StringOrNumber.parse(true); // ❌ Throws ZodError
Intersection Types
Combine two schemas into one.
const Person = z.object({ name: z.string() });
const Age = z.object({ age: z.number() });
const PersonWithAge = Person.and(Age);
PersonWithAge.parse({ name: "Alice", age: 30 }); // ✅ Valid
PersonWithAge.parse({ name: "Alice" }); // ❌ Throws ZodError
Optional and Nullable Fields
Making a Field Optional
const User = z.object({
username: z.string(),
bio: z.string().optional()
});
User.parse({ username: "Alice" }); // ✅ Valid (bio is optional)
User.parse({ username: "Alice", bio: "Hello" }); // ✅ Valid
User.parse({ bio: "Hello" }); // ❌ Invalid (username is required)
Allowing Null Values
const Post = z.object({
title: z.string(),
content: z.string().nullable()
});
Post.parse({ title: "Hello", content: null }); // ✅ Valid
Post.parse({ title: "Hello", content: "Text" }); // ✅ Valid
Post.parse({ title: "Hello" }); // ❌ Invalid (content is required)
Refining and Custom Validations
Using .refine()
for Custom Validation
You can add additional constraints beyond basic type checking.
const PositiveNumber = z.number().refine(n => n > 0, {
message: "Number must be positive"
});
PositiveNumber.parse(10); // ✅ Valid
PositiveNumber.parse(-5); // ❌ Throws ZodError ("Number must be positive")
Using .superRefine()
for More Complex Validations
const Password = z.string().superRefine((val, ctx) => {
if (val.length < 8) {
ctx.addIssue({
code: "custom",
message: "Password must be at least 8 characters long"
});
}
});
Password.parse("short"); // ❌ Throws ZodError
Password.parse("longenoughpassword"); // ✅ Valid
Transforming Data
Zod allows you to modify values during validation.
const TrimmedString = z.string().transform(str => str.trim());
TrimmedString.parse(" hello "); // ✅ "hello"
Chaining multiple transformations:
const UserName = z.string().trim().toLowerCase();
UserName.parse(" ALICE "); // ✅ "alice"
Asynchronous Validations
Zod supports async validation, useful for database lookups or API calls.
const EmailSchema = z
.string()
.email()
.refine(async email => {
const isAvailable = await checkEmailAvailability(email);
return isAvailable;
}, "Email is already taken");
EmailSchema.parseAsync("user@example.com").then(console.log).catch(console.error);
Conclusion
Zod provides a rich set of features for defining, validating, and transforming data with minimal effort. Whether you're working with form validation, API request handling, or TypeScript type safety, Zod is a powerful tool that simplifies data management.
To explore more advanced features and real-world use cases, check out Table of Contents.