zod is a schema validation library for TypeScript which provides type safety for incoming objects.
Basic Usage
import { z } from "zod";
const User = z.object({
name: z.string(),
age: z.number(),
});
type User = z.infer<typeof User>;
User.parse(valid); // { success: true, data: ... }
// This throws an error!
User.parse(invalid); // { success: false, error: ZodError }
// This does not throw an error :)
User.safeParse(invalid); // { success: false, error: ZodError }
Type Coercion and Transformation
Coercion is the method of passing a given value through JS's native constructor for that particular type before doing Zod parsing as normal.
All primitive types (string
, number
, boolean
, bigint
, date
) support coercion. For non-primitive types, see below.
const Age = z.coerce.number();
Age.parse("55"); // 55
Age.parse("abcd"); // { success: false, error: ZodError }
For explicit type transformation, use the .transform()
method.
Note
This will transform the data after it has been validated.**
const Age = z.string().min(1).max(3).transform(Number);
Age.parse("1"); // 1
Age.parse("1111"); // { ..., code: "too_big", max: 3 }
For type transformation before validation, use the .preprocess()
method.
Note
For any primitive type, coercion is the preferred method.**
const NumberList = z.preprocess(
(val: string) => input.split(",").map(Number),
z.number().array()
);
NumberList.parse("1,2,3,4"); // [ 1, 2, 3, 4 ]
Error Handling
Errors are handled in Zod through the ZodError
type.
When using .parse()
, these errors will be returned AND thrown. When using .safeParse()
, these errors will only be returned.
To raise a custom error, you need to use the ZodError
constructor. This is made up of an array of ZodIssue
s. Issues contain a code
of "custom"
, a message describing the issue, and a path of where the issue originated.
const FirstName = z.string().max(50);
type FirstName = z.infer<typeof FirstName>;
const parseFirstName = (input: unknown): FirstName => {
if (input === "Aaron") {
throw new ZodError([
{
code: "custom",
message: "My name is TAKEN!",
path: [],
},
]);
}
return FirstName.parse(input);
};
Types
Empty types
z.undefined();
z.null();
z.void(); // Accepts undefined.
Enums
Zod provides both Zod-native and TypeScript-native enums.
Inferring a z.enum
will output a tuple of strings type.
const FruitEnum = z.enum(["Banana", "Apple", "Mango"]);
// 'Banana' | 'Apple' | 'Mango'
type Fruit = z.infer<typeof FruitEnum>;
Alternatively, you can define an as const
array of possible values.
// Without the 'as const' this would not be allowed,
// since zod cannot infer the type at compile time.
const FRUITS = ["Banana", "Apple", "Mango"] as const;
const FruitEnum = z.enum(FRUITS);
type Fruit = z.infer<typeof FruitEnum>;
You can use native enums, but you rarely should.
Unions
For values that could be one of a few things, Zod provides union (OR) types.
const NutEnum = z.enum(["Macadamia", "Almond", "Chestnut"]);
const Food = z.union([FruitEnum, NutEnum]);
Tuples
A tuple in Zod is defined as an array of fixed-length with elements of specific types.
const SaleRecord = z.tuple([
z.string(), // Item sold
z.number(), // Price sold for
z.string().datetime(), // Time sale was made
]);
type Sale = z.infer<typeof SaleRecord>;
Arrays
An array in Zod is defined as either z.type().array()
or z.array(z.type())
.
For an optional array vs array of optional values, ordering matters.
// (string | undefined)[]
const optionalStringArray = z.string().optional().array();
// string[] | undefined
const stringArrayOrOptional = z.string().array().optional();