zod
Zod TypeScript schema validation library for runtime type checking. USE WHEN: user mentions "Zod", "schema validation", "form validation", "API validation", "type-safe validation", asks about "runtime validation", "React Hook Form validation" DO NOT USE FOR: Yup projects (use yup skill), class-validator/NestJS DTOs (use class-validator skill), PropTypes, compile-time only validation
What this skill does
# Zod - Quick Reference
## When to Use This Skill
- Form validation with React Hook Form
- API data parsing/validation
- Type inference from schemas
> **Deep Knowledge**: Use `mcp__documentation__fetch_docs` with technology: `zod` for comprehensive documentation.
## Essential Patterns
### Basic Schema
```typescript
import { z } from 'zod';
const userSchema = z.object({
name: z.string().min(2),
email: z.string().email(),
age: z.number().min(18).optional(),
role: z.enum(['admin', 'user'])
});
// Type inference
type User = z.infer<typeof userSchema>;
// Parse
const user = userSchema.parse(data); // throws ZodError
const result = userSchema.safeParse(data); // { success, data/error }
```
### Common Validations
```typescript
// String
z.string().min(1).max(100).email().url().uuid()
// Number
z.number().int().positive().min(0).max(100)
// Custom validation
z.string().refine(val => val.startsWith('@'), "Must start with @")
// Object cross-field
z.object({
password: z.string(),
confirm: z.string()
}).refine(d => d.password === d.confirm, {
message: "Passwords don't match",
path: ['confirm']
});
```
### React Hook Form
```tsx
import { zodResolver } from '@hookform/resolvers/zod';
const { register, handleSubmit, formState: { errors } } = useForm({
resolver: zodResolver(userSchema)
});
<input {...register('name')} />
{errors.name && <span>{errors.name.message}</span>}
```
### Schema Operations
```typescript
userSchema.partial() // All fields optional
userSchema.pick({ name: true }) // Pick fields
userSchema.omit({ id: true }) // Omit fields
userSchema.extend({ phone: z.string() }) // Extend
```
## When NOT to Use This Skill
- **Yup existing projects** - Use `yup` skill for Formik integration
- **NestJS DTOs** - Use `class-validator` skill for decorator-based validation
- **Compile-time only checks** - TypeScript types are sufficient
- **Simple PropTypes** - React PropTypes might be enough
## Anti-Patterns to Avoid
- Do not define schemas inside components (performance)
- Do not use `.parse()` for user input without try/catch
- Do not forget custom error messages
## Anti-Patterns
| Anti-Pattern | Why It's Bad | Correct Approach |
|--------------|--------------|------------------|
| Schema inside components | Re-created on every render | Define at module level |
| Using .parse() without try-catch | Throws unhandled errors | Use .safeParse() for user input |
| No custom error messages | Generic "Invalid input" | Add custom messages to all validations |
| Validating twice (FE + BE) | Performance waste | Share schemas between client/server |
| Not using z.infer | Manual type definitions | Always use type inference |
| Ignoring .transform() | Miss data normalization | Use transforms for trimming, etc. |
## Quick Troubleshooting
| Issue | Cause | Solution |
|-------|-------|----------|
| "Expected string, received number" | Type mismatch | Use z.coerce.string() for type coercion |
| Performance issues | Schema in component | Move schema to module level |
| Async validation not working | Using .parse() | Use .parseAsync() for async refinements |
| Nested errors not showing | Not using flatten() | Use error.flatten() for nested structures |
| "Invalid type" on optional fields | Using undefined | Use .optional() or .nullable() |
| Form not validating | Resolver not set | Add resolver: zodResolver(schema) |
## Production Readiness
### Error Handling
```typescript
import { z, ZodError } from 'zod';
// Safe parsing with error handling
function validateInput<T>(schema: z.ZodSchema<T>, data: unknown): T {
const result = schema.safeParse(data);
if (!result.success) {
const errors = result.error.flatten();
throw new ValidationError(errors);
}
return result.data;
}
// Custom error messages
const userSchema = z.object({
email: z.string({
required_error: 'Email is required',
invalid_type_error: 'Email must be a string',
}).email({ message: 'Invalid email format' }),
password: z.string()
.min(8, { message: 'Password must be at least 8 characters' })
.regex(/[A-Z]/, { message: 'Password must contain uppercase letter' })
.regex(/[0-9]/, { message: 'Password must contain number' }),
});
// Error formatting for API responses
function formatZodError(error: ZodError): Record<string, string[]> {
return error.flatten().fieldErrors as Record<string, string[]>;
}
```
### API Integration
```typescript
// Express middleware
import { Request, Response, NextFunction } from 'express';
function validate<T extends z.ZodSchema>(schema: T) {
return (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
return res.status(400).json({
code: 'VALIDATION_ERROR',
errors: formatZodError(result.error),
});
}
req.body = result.data;
next();
};
}
// Usage
app.post('/users', validate(createUserSchema), createUser);
// tRPC integration
export const appRouter = t.router({
createUser: t.procedure
.input(createUserSchema)
.mutation(({ input }) => {
// input is fully typed and validated
return db.users.create({ data: input });
}),
});
```
### Complex Validations
```typescript
// Conditional fields
const formSchema = z.discriminatedUnion('type', [
z.object({
type: z.literal('individual'),
name: z.string(),
ssn: z.string().regex(/^\d{3}-\d{2}-\d{4}$/),
}),
z.object({
type: z.literal('business'),
companyName: z.string(),
ein: z.string().regex(/^\d{2}-\d{7}$/),
}),
]);
// Async validation
const uniqueEmailSchema = z.string().email().refine(
async (email) => {
const exists = await db.users.findByEmail(email);
return !exists;
},
{ message: 'Email already registered' }
);
// Transform and validate
const dateSchema = z.string()
.transform((str) => new Date(str))
.refine((date) => !isNaN(date.getTime()), 'Invalid date');
// Preprocess for type coercion
const querySchema = z.object({
page: z.preprocess(
(val) => parseInt(val as string, 10),
z.number().min(1).default(1)
),
limit: z.preprocess(
(val) => parseInt(val as string, 10),
z.number().min(1).max(100).default(20)
),
});
```
### Testing
```typescript
import { describe, it, expect } from 'vitest';
describe('userSchema', () => {
it('validates correct input', () => {
const input = { email: '[email protected]', password: 'Password1!' };
const result = userSchema.safeParse(input);
expect(result.success).toBe(true);
if (result.success) {
expect(result.data.email).toBe('[email protected]');
}
});
it('fails on invalid email', () => {
const input = { email: 'invalid', password: 'Password1!' };
const result = userSchema.safeParse(input);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.flatten().fieldErrors.email).toContain('Invalid email format');
}
});
it('fails on weak password', () => {
const input = { email: '[email protected]', password: 'weak' };
const result = userSchema.safeParse(input);
expect(result.success).toBe(false);
});
});
// Property-based testing with fast-check
import * as fc from 'fast-check';
it('accepts all valid emails', () => {
fc.assert(
fc.property(fc.emailAddress(), (email) => {
const result = z.string().email().safeParse(email);
return result.success;
})
);
});
```
### Performance
```typescript
// Define schemas outside components/functions
const schemas = {
user: z.object({ /* ... */ }),
product: z.object({ /* ... */ }),
} as const;
// Lazy loading for circular references
const categorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
name: z.string(),
children: z.array(categorySchema).optional(),
})
);
// Coercion for better performance on known types
const numberSchema = z.coerce.number(); // Auto-converts strings
const dateSchema = z.coerce.date();
```
### MonRelated in Web Dev
generating-lwc-components
IncludedLightning Web Components with PICKLES methodology and 165-point scoring. Use this skill when the user creates or edits LWC components, builds wire service patterns, or writes Jest tests for LWC. TRIGGER when: user creates/edits LWC components, touches lwc/**/*.js, .html, .css, .js-meta.xml files, or asks about wire service, SLDS, or Jest LWC tests. DO NOT TRIGGER when: Apex classes (use generating-apex), Aura components, or Visualforce.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Set up queries with useQuery, mutations with useMutation, configure QueryClient caching strategies, implement optimistic updates, and handle infinite scroll with useInfiniteQuery. Use when: setting up data fetching in React projects, migrating from v4 to v5, or fixing object syntax required errors, query callbacks removed issues, cacheTime renamed to gcTime, isPending vs isLoading confusion, keepPreviousData removed problems.
document-processor-api
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
nutrient-document-processing
IncludedProcess documents with Nutrient DWS. Use when the user wants to generate PDFs from HTML or URLs, convert Office/images/PDFs, assemble or split packets, OCR scans, extract text/tables/key-value pairs, redact PII, watermark, sign, fill forms, optimize PDFs, or produce compliance outputs like PDF/A or PDF/UA. Triggers include convert to PDF, merge these PDFs, OCR this scan, extract tables, redact PII, sign this PDF, make this PDF/A, or linearize for web delivery.
tanstack-query
IncludedManage server state in React with TanStack Query v5. Covers useMutationState, simplified optimistic updates, throwOnError, network mode (offline/PWA), and infiniteQueryOptions. Use when setting up data fetching, fixing v4→v5 migration errors (object syntax, gcTime, isPending, keepPreviousData), or debugging SSR/hydration issues with streaming server components.
accelint-nextjs-best-practices
IncludedNext.js performance optimization and best practices. Use when writing Next.js code (App Router or Pages Router); implementing Server Components, Server Actions, or API routes; optimizing RSC serialization, data fetching, or server-side rendering; reviewing Next.js code for performance issues; fixing authentication in Server Actions; or implementing Suspense boundaries, parallel data fetching, or request deduplication.