Notre avis
Expertise en validation de schémas Zod, incluant l'inférence de types, la composition, l'analyse, les raffinements, les transformations et la gestion des erreurs.
Points forts
- Inférence automatique des types TypeScript à partir des schémas
- Composition facile de schémas complexes à partir de schémas simples
- Validation riche avec des méthodes intégrées (email, URL, regex, etc.)
- Messages d'erreur détaillés et personnalisables
Limites
- Courbe d'apprentissage pour les fonctionnalités avancées comme les raffinements et transformations
- Nécessite une intégration supplémentaire pour la validation de formulaires
- Surcharge de performance pour les schémas très complexes
Lors de la construction d'applications TypeScript nécessitant une validation runtime des données, des entrées API ou des champs de formulaire.
Pour une validation simple n'ayant pas besoin d'inférence de types, ou lorsque la performance est critique et la validation minimale.
Analyse de sécurité
SûrThe skill is a guidance document for using the Zod validation library, containing only safe code examples and the standard npm install command. No destructive or data-exfiltrating actions are included.
Aucun point d'attention détecté
Exemples
Help me create a Zod schema for a user registration form with fields: username (string, 3-20 chars), email (valid email), password (min 8 chars, must contain uppercase and number), and age (optional number, min 18). Also infer the TypeScript type.How do I define a discriminated union in Zod for an API response that can be either a success (with data) or an error (with message and code)?I need a Zod schema that validates a string is a valid hex color code (e.g., #FF5733). How can I use refinements for custom validation logic?name: Zod description: Expert guidance for Zod schema validation including type inference, schema composition, parsing, refinements, transformations, error handling, and TypeScript integration. Use this when building type-safe validation, form validation, or API input validation.
Zod
Expert assistance with Zod - TypeScript-first schema validation.
Overview
Zod is a TypeScript-first schema declaration and validation library:
- Type Inference: Automatic TypeScript type inference
- Zero Dependencies: No runtime dependencies
- Composable: Build complex schemas from simple ones
- Developer Experience: Excellent autocomplete and error messages
Installation
npm install zod
Basic Usage
import { z } from 'zod';
// Define schema
const userSchema = z.object({
name: z.string(),
age: z.number(),
email: z.string().email(),
});
// Infer TypeScript type
type User = z.infer<typeof userSchema>;
// type User = { name: string; age: number; email: string }
// Parse data (throws on validation error)
const user = userSchema.parse({
name: 'John',
age: 30,
email: 'john@example.com',
});
// Safe parse (returns result object)
const result = userSchema.safeParse({ name: 'John', age: '30' });
if (result.success) {
console.log(result.data);
} else {
console.error(result.error);
}
Primitive Types
// String
z.string();
z.string().min(5);
z.string().max(100);
z.string().length(10);
z.string().email();
z.string().url();
z.string().uuid();
z.string().regex(/^[a-z]+$/);
z.string().startsWith('https://');
z.string().endsWith('.com');
// Number
z.number();
z.number().int();
z.number().positive();
z.number().negative();
z.number().min(0);
z.number().max(100);
z.number().multipleOf(5);
// Boolean
z.boolean();
// Date
z.date();
z.date().min(new Date('2024-01-01'));
z.date().max(new Date('2025-01-01'));
// Literal
z.literal('admin');
z.literal(42);
z.literal(true);
Complex Types
// Object
const userSchema = z.object({
name: z.string(),
age: z.number(),
});
// Array
z.array(z.string());
z.array(z.number()).min(1).max(10);
// Tuple
z.tuple([z.string(), z.number(), z.boolean()]);
// Union (OR)
z.union([z.string(), z.number()]);
z.string().or(z.number()); // Same as above
// Discriminated Union
const shapeSchema = z.discriminatedUnion('kind', [
z.object({ kind: z.literal('circle'), radius: z.number() }),
z.object({ kind: z.literal('rectangle'), width: z.number(), height: z.number() }),
]);
// Intersection (AND)
const baseUser = z.object({ id: z.string() });
const namedUser = z.object({ name: z.string() });
const user = z.intersection(baseUser, namedUser);
// Or use extend
const user = baseUser.extend({ name: z.string() });
// Enum
z.enum(['admin', 'user', 'guest']);
z.nativeEnum(MyEnum);
// Record
z.record(z.string()); // { [key: string]: string }
z.record(z.string(), z.number()); // { [key: string]: number }
// Map
z.map(z.string(), z.number());
// Set
z.set(z.string());
Modifiers
// Optional
z.string().optional(); // string | undefined
z.object({ name: z.string().optional() });
// Nullable
z.string().nullable(); // string | null
// Nullish (optional + nullable)
z.string().nullish(); // string | null | undefined
// Default
z.string().default('default value');
z.number().default(0);
// Catch (provide fallback on parse error)
z.string().catch('fallback');
Refinements
// Custom validation
const passwordSchema = z.string().refine(
(val) => val.length >= 8,
{ message: 'Password must be at least 8 characters' }
);
// Multiple refinements
const schema = z.string()
.min(8)
.refine((val) => /[A-Z]/.test(val), {
message: 'Must contain uppercase letter',
})
.refine((val) => /[0-9]/.test(val), {
message: 'Must contain number',
});
// Superrefine (access ctx for multiple errors)
const schema = z.string().superRefine((val, ctx) => {
if (val.length < 8) {
ctx.addIssue({
code: z.ZodIssueCode.too_small,
minimum: 8,
type: 'string',
inclusive: true,
message: 'Too short',
});
}
if (!/[A-Z]/.test(val)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Must contain uppercase',
});
}
});
Transformations
// Transform value
const schema = z.string().transform((val) => val.toLowerCase());
// Chain transforms
const schema = z.string()
.transform((val) => val.trim())
.transform((val) => val.toLowerCase());
// Transform to different type
const numberSchema = z.string().transform((val) => parseInt(val, 10));
// Preprocess before validation
const schema = z.preprocess(
(val) => (typeof val === 'string' ? val.trim() : val),
z.string().min(1)
);
Object Methods
const userSchema = z.object({
id: z.string(),
name: z.string(),
email: z.string(),
age: z.number(),
});
// Pick fields
const nameOnly = userSchema.pick({ name: true });
// Omit fields
const withoutId = userSchema.omit({ id: true });
// Partial (all fields optional)
const partialUser = userSchema.partial();
// Deep Partial
const deepPartial = userSchema.deepPartial();
// Required (make all fields required)
const required = partialUser.required();
// Extend
const extendedUser = userSchema.extend({
role: z.enum(['admin', 'user']),
});
// Merge
const merged = userSchema.merge(z.object({ role: z.string() }));
// Passthrough (allow extra fields)
const schema = userSchema.passthrough();
// Strict (disallow extra fields)
const schema = userSchema.strict();
// Strip (remove extra fields, default)
const schema = userSchema.strip();
Error Handling
const schema = z.object({
name: z.string().min(2),
age: z.number().min(18),
});
const result = schema.safeParse({ name: 'J', age: 15 });
if (!result.success) {
// Zod error object
console.log(result.error);
// Format errors
console.log(result.error.format());
/*
{
name: { _errors: ['String must contain at least 2 characters'] },
age: { _errors: ['Number must be greater than or equal to 18'] }
}
*/
// Flatten errors
console.log(result.error.flatten());
/*
{
formErrors: [],
fieldErrors: {
name: ['String must contain at least 2 characters'],
age: ['Number must be greater than or equal to 18']
}
}
*/
// Get first error
console.log(result.error.issues[0]);
}
// Custom error messages
const schema = z.string().min(5, { message: 'Too short!' });
const schema = z.string().email({ message: 'Invalid email address' });
// Custom error map
const schema = z.string().min(5, 'Custom error');
Async Validation
// Async refinement
const schema = z.string().refine(
async (email) => {
const exists = await checkEmailExists(email);
return !exists;
},
{ message: 'Email already exists' }
);
// Parse async
const result = await schema.parseAsync('test@example.com');
const result = await schema.safeParseAsync('test@example.com');
React Hook Form Integration
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const formSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
age: z.number().min(18, 'Must be 18 or older'),
});
type FormData = z.infer<typeof formSchema>;
function MyForm() {
const { register, handleSubmit, formState: { errors } } = useForm<FormData>({
resolver: zodResolver(formSchema),
});
const onSubmit = (data: FormData) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('name')} />
{errors.name && <span>{errors.name.message}</span>}
<input {...register('email')} />
{errors.email && <span>{errors.email.message}</span>}
<input {...register('age', { valueAsNumber: true })} type="number" />
{errors.age && <span>{errors.age.message}</span>}
<button type="submit">Submit</button>
</form>
);
}
tRPC Integration
import { z } from 'zod';
import { publicProcedure, router } from './trpc';
const createUserSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
});
export const userRouter = router({
create: publicProcedure
.input(createUserSchema)
.mutation(({ input }) => {
// input is fully typed!
const { name, email } = input;
return createUser({ name, email });
}),
});
Common Patterns
PKI Certificate Validation
const distinguishedNameSchema = z.object({
commonName: z.string().min(1),
organization: z.string().optional(),
organizationalUnit: z.string().optional(),
country: z.string().length(2).optional(),
state: z.string().optional(),
locality: z.string().optional(),
});
const certificateSchema = z.object({
subject: distinguishedNameSchema,
issuer: distinguishedNameSchema,
serialNumber: z.string(),
notBefore: z.date(),
notAfter: z.date(),
keyUsage: z.array(z.enum([
'digitalSignature',
'nonRepudiation',
'keyEncipherment',
'dataEncipherment',
'keyAgreement',
'keyCertSign',
'cRLSign',
])),
extendedKeyUsage: z.array(z.enum([
'serverAuth',
'clientAuth',
'codeSigning',
'emailProtection',
'timeStamping',
'OCSPSigning',
])).optional(),
subjectAlternativeNames: z.array(z.string()).optional(),
}).refine(
(data) => data.notAfter > data.notBefore,
{ message: 'notAfter must be after notBefore' }
);
API Response Validation
const apiResponseSchema = z.object({
success: z.boolean(),
data: z.unknown().optional(),
error: z.object({
code: z.string(),
message: z.string(),
}).optional(),
}).refine(
(data) => data.success ? data.data !== undefined : data.error !== undefined,
{ message: 'Response must have data if success, or error if not' }
);
Best Practices
- Type Inference: Always use
z.infer<typeof schema>for types - Reusable Schemas: Define common schemas once, reuse everywhere
- Composition: Build complex schemas from simple ones
- Error Messages: Provide clear custom error messages
- safeParse: Use
safeParsewhen you want to handle errors yourself - Transformations: Use transforms to normalize data
- Refinements: Use refinements for complex business logic
- Optional vs Nullable: Understand the difference
- Strict Mode: Use
.strict()on objects to catch extra fields - Documentation: Add JSDoc comments to schemas
Resources
- Documentation: https://zod.dev
- GitHub: https://github.com/colinhacks/zod
Expert Next.js App Router
Developpement
Un skill qui transforme Claude en expert Next.js App Router.
Générateur de README
Developpement
Crée des README.md professionnels et complets pour vos projets.
Rédacteur de Documentation API
Developpement
Génère de la documentation API complète au format OpenAPI/Swagger.