| name | zod-3 |
| description | Zod 3 validation schemas and patterns. Trigger: When creating Zod validation schemas for API input validation.
|
| license | MIT |
| metadata | {"author":"migestion","version":"1.0","scope":["api"],"auto_invoke":"Creating Zod schemas"} |
| allowed-tools | Read, Edit, Write, Glob, Grep, Bash, WebFetch, WebSearch, Task |
Basic Schema (REQUIRED)
import { z } from 'zod';
export const createClientSchema = z.object({
companyName: z.string().min(1).max(100),
contactName: z.string().min(1).max(100),
email: z.string().email().optional().or(z.literal('')),
phone: z.string().optional().or(z.literal('')),
status: z.enum(['active', 'inactive', 'pending']),
segment: z.string().optional().or(z.literal('')),
tags: z.array(z.string()).optional(),
notes: z.string().optional().or(z.literal('')),
});
export type CreateClientInput = z.infer<typeof createClientSchema>;
String Validation
z.string();
z.string().min(3);
z.string().max(100);
z.string().length(10);
z.string().email();
z.string().url();
z.string().uuid();
z.string().regex(/^[A-Z]/);
z.string().trim();
z.string().optional();
z.string().nullable();
z.string().default('');
Number Validation
z.number();
z.number().min(0);
z.number().max(100);
z.number().int();
z.number().positive();
z.number().nonnegative();
z.number().optional();
z.number().default(0);
Boolean
z.boolean();
z.boolean().optional();
z.boolean().default(false);
Enums
z.enum(['active', 'inactive', 'pending']);
z.enum(['active', 'inactive']).default('active');
Arrays
z.array(z.string());
z.array(z.string()).min(1);
z.array(z.string()).max(10);
z.array(z.string()).length(5);
z.array(z.string()).optional();
z.array(z.string()).default([]);
Objects
z.object({
name: z.string(),
age: z.number().optional(),
});
z.object({}).strict();
z.object({}).passthrough();
Nested Objects
z.object({
user: z.object({
name: z.string(),
email: z.string().email(),
}),
});
Optional vs Nullable
z.string().optional();
z.string().nullable();
z.string().optional().nullable();
z.string().or(z.literal(''));
Default Values
z.string().default('default value');
z.number().default(0);
z.boolean().default(false);
z.array(z.string()).default([]);
Refinement (Custom Validation)
z.string().refine(val => val.length >= 3, 'Must be at least 3 characters');
z.string().refine(async val => await isUniqueEmail(val), 'Email already exists');
z.string().transform(val => val.toLowerCase());
Union
z.union([z.string(), z.number()]);
z.discriminatedUnion('type', [
z.object({ type: z.literal('a'), value: z.string() }),
z.object({ type: z.literal('b'), value: z.number() }),
]);
Literals
z.literal('active');
z.literal(true);
z.literal(42);
z.array(z.literal('tag1', 'tag2', 'tag3'));
Date Validation
z.string().datetime();
z.string().date();
z.string().time();
UUID
z.string().uuid();
z.string().uuid().optional();
Email Validation
z.string().email();
z.string().email().optional().or(z.literal(''));
Password Validation
const passwordSchema = z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Must contain uppercase letter')
.regex(/[a-z]/, 'Must contain lowercase letter')
.regex(/[0-9]/, 'Must contain number');
Query Params
export const listClientsQuerySchema = z.object({
page: z.coerce.number().min(1).default(1),
limit: z.coerce.number().min(1).max(100).default(20),
search: z.string().optional(),
status: z.enum(['active', 'inactive', 'pending']).optional(),
sortBy: z.string().default('createdAt'),
sortOrder: z.enum(['asc', 'desc']).default('desc'),
});
export type ListClientsQuery = z.infer<typeof listClientsQuerySchema>;
Validation in Express
export const validateBody = <T>(schema: z.ZodSchema<T>) => {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
success: false,
errors: result.error.errors,
});
}
req.body = result.data;
next();
};
};
router.post('/clients', validateBody(createClientSchema), createHandler);
Parse and Type Inference
type CreateClientInput = z.infer<typeof createClientSchema>;
const result = createClientSchema.parse(req.body);
const result = createClientSchema.safeParse(req.body);
if (result.success) {
const data = result.data;
} else {
console.log(result.error.errors);
}
Error Format
{
"success": false,
"errors": [
{
"code": "invalid_type",
"expected": "string",
"received": "undefined",
"path": ["companyName"],
"message": "Required"
}
]
}
Related Skills
migestion-api - API validation patterns
typescript - TypeScript patterns