Error Handling
Zod provides comprehensive error handling capabilities to help you manage validation errors.
Basic Error Handling
ts
import { z } from "zod";
const schema = z.string();
try {
schema.parse(42); // throws ZodError
} catch (error) {
if (error instanceof z.ZodError) {
console.log(error.issues);
// [
// {
// code: "invalid_type",
// expected: "string",
// received: "number",
// path: [],
// message: "Expected string, received number"
// }
// ]
}
}
Custom Error Messages
ts
// Set error messages when creating schemas
const User = z.object({
username: z.string({
required_error: "Username is required",
invalid_type_error: "Username must be a string",
}),
age: z.number({
required_error: "Age is required",
invalid_type_error: "Age must be a number",
}),
});
// Use .refine to add custom validation and error messages
const ageSchema = z.number()
.refine(n => n >= 0, {
message: "Age cannot be negative",
})
.refine(n => n <= 120, {
message: "Age cannot exceed 120",
});
Error Formatting
ts
const schema = z.object({
name: z.string(),
age: z.number(),
email: z.string().email(),
});
try {
schema.parse({
name: 42,
age: "not a number",
email: "invalid-email",
});
} catch (error) {
if (error instanceof z.ZodError) {
// Flatten error messages
console.log(error.flatten());
// {
// formErrors: [],
// fieldErrors: {
// name: ["Expected string, received number"],
// age: ["Expected number, received string"],
// email: ["Invalid email"]
// }
// }
// Get all error messages
console.log(error.errors);
// [
// { path: ["name"], message: "Expected string, received number" },
// { path: ["age"], message: "Expected number, received string" },
// { path: ["email"], message: "Invalid email" }
// ]
}
}
Error Mapping
ts
// Global error map
const customErrorMap: z.ZodErrorMap = (issue, ctx) => {
if (issue.code === z.ZodIssueCode.invalid_type) {
if (issue.expected === "string") {
return { message: "This field must be a string" };
}
}
if (issue.code === z.ZodIssueCode.too_small) {
return { message: "Value is too small" };
}
return { message: ctx.defaultError };
};
z.setErrorMap(customErrorMap);
// Schema-specific error mapping
const schema = z.string().transform((val) => val.toUpperCase(), {
errorMap: (issue, ctx) => ({
message: "Transform failed: " + ctx.defaultError,
}),
});
Async Validation Errors
ts
const asyncSchema = z.string().refine(async (val) => {
const response = await fetch(`/api/check-username/${val}`);
return response.ok;
}, {
message: "Username is already taken",
});
try {
await asyncSchema.parseAsync("username");
} catch (error) {
if (error instanceof z.ZodError) {
console.log(error.issues);
}
}
Error Handling Best Practices
ts
// 1. Use type guards
function isZodError(error: unknown): error is z.ZodError {
return error instanceof z.ZodError;
}
// 2. Create error handling utility functions
function handleZodError(error: unknown) {
if (isZodError(error)) {
return {
success: false,
errors: error.flatten().fieldErrors,
};
}
return {
success: false,
errors: { _errors: ["An unknown error occurred"] },
};
}
// 3. Use in API routes
async function handleRequest(data: unknown) {
try {
const validData = await schema.parseAsync(data);
// Process validated data
return { success: true, data: validData };
} catch (error) {
return handleZodError(error);
}
}
// 4. Combine multiple validations
const combinedSchema = z.object({
user: User,
preferences: Preferences,
}).refine(
(data) => {
// Complex cross-field validation
return true;
},
{
message: "Validation failed",
path: ["user", "preferences"], // Specify error path
}
);