zod
A Zod v4 validation specialist.
What this skill does
# Zod
This skill provides guidance for implementing type-safe validation using Zod v4 in TypeScript applications. It covers schema design, error handling, type inference, and migration from Zod 3.
## Zod 4 Requirements
This skill is exclusively for **Zod 4**, which introduced breaking changes from Zod 3. All examples and recommendations use Zod 4 syntax.
### Installation
```bash
npm install zod@^4.0.0
```
### Critical Zod 4 Changes
If you encounter Zod 3 code or examples, be aware of these breaking changes:
**Error Customization - Use `error` not `message`**
```typescript
z.string().min(5, { error: 'Too short.' });
z.string().min(5, { message: 'Too short.' });
```
**String Formats - Use top-level functions**
```typescript
z.email();
z.uuid();
z.url();
z.iso.date();
z.string().email();
```
**Object Methods - Use dedicated functions**
```typescript
z.strictObject({ name: z.string() });
z.looseObject({ name: z.string() });
z.object({ name: z.string() }).strict();
z.object({ name: z.string() }).passthrough();
```
**Error Formatting - Use top-level functions**
```typescript
z.flattenError(error);
z.treeifyError(error);
z.prettifyError(error);
error.flatten();
error.format();
```
**Function Schemas - New syntax**
```typescript
const myFn = z.function({
input: [z.string()],
output: z.number(),
});
const myFn = z.function().args(z.string()).returns(z.number());
```
**Enums - Unified API**
```typescript
enum Color {
Red = 'red',
Green = 'green',
}
z.enum(Color);
z.nativeEnum(Color);
```
**Deprecated APIs to avoid:**
- `invalid_type_error` and `required_error` parameters (use `error` function instead)
- `.merge()` on objects (use `.extend()` or object spread)
- `.deepPartial()` (removed, anti-pattern)
- `z.promise()` (rarely needed, just await the promise)
- Single-argument `z.record()` (now requires both key and value schemas)
### Key Improvements in Zod 4
- **Performance**: Dramatically faster parsing and validation
- **Error handling**: Unified `error` parameter for all error customization
- **Type safety**: Better TypeScript inference and type narrowing
- **Tree-shaking**: Top-level functions are more tree-shakable
- **Refinements**: Now stored inside schemas, not wrapper classes
- **Defaults in optional fields**: Applied correctly within optional properties
## Core Principles
1. Follow coding guidelines strictly (SRP, single export per file, prefer interfaces over types)
2. Create properly structured schema files with kebab-case naming
3. Leverage Zod's type inference for compile-time type safety
4. Implement proper error handling with safeParse for user-facing validations
5. Use refinements for custom validation logic
6. Organize schemas in a dedicated schemas directory structure
## Common Use Cases
### 1. API Request/Response Validation
When implementing API validation:
- Create schema files in `src/schemas/` or appropriate directory
- Export a single schema per file using kebab-case naming
- Use `.safeParse()` for user input to handle errors gracefully
- Use `.parse()` only when input is guaranteed to be valid
- Infer TypeScript types from schemas using `z.infer<>`
Example structure:
```
src/
schemas/
user-create-request.schema.ts
user-response.schema.ts
interfaces/
user.interface.ts
functions/
validate-user-request.ts
```
### 2. Form Validation
For form validation:
- Create schemas matching form structure
- Use `.safeParse()` to validate on submit
- Extract and display field-specific errors using `z.flattenError()`
- Implement real-time validation with debouncing if needed
- Use refinements for cross-field validation (e.g., password confirmation)
### 3. Environment Variable Validation
For environment validation:
- Create a schema in `src/schemas/environment.schema.ts`
- Validate on application startup using `.parse()`
- Let it throw if environment is invalid (fail-fast approach)
- Export inferred type for use throughout the application
### 4. Schema Organization
Follow this structure:
- Simple schemas: Direct export from schema file
- Complex schemas: Build from smaller schemas
- Shared schemas: Create base schemas and extend/pick as needed
- Keep one schema export per file (following SRP)
### 5. Type Inference and Interfaces
- Use `z.infer<typeof Schema>` to extract types
- Save inferred types as interfaces in the interfaces directory
- Use interfaces instead of inline types throughout the codebase
- Export one interface per file with kebab-case naming
### 6. Error Handling Patterns
Implement these patterns:
- **User input**: Use `.safeParse()` and handle errors gracefully
- **Internal validation**: Use `.parse()` to fail fast on programming errors
- **Async validation**: Use `.parseAsync()` or `.safeParseAsync()` with async validations
- **Custom errors**: Use the `error` parameter for user-friendly messages
- **Formatted errors**: Use `z.flattenError()` for forms, `z.treeifyError()` for nested data
### 7. Understanding Error Structure
When validation fails, Zod returns a `ZodError` instance containing an `.issues` array. Each issue provides granular information about what went wrong.
#### The Issues Array
Every validation error contains detailed metadata:
```typescript
const result = schema.safeParse(invalidData);
if (!result.success) {
result.error.issues;
}
```
Each issue object contains:
- **code**: Error code indicating the type of validation failure
- `invalid_type`: Wrong data type (e.g., expected string, got number)
- `custom`: Custom validation from `.refine()` or `.superRefine()`
- `too_big`: Value exceeds maximum constraint
- `too_small`: Value below minimum constraint
- `unrecognized_keys`: Extra keys in strict objects
- `invalid_string`: String format validation failed (email, url, etc.)
- And many more specific codes
- **path**: Array showing the location of the error in nested structures
- `[]` for top-level errors
- `['username']` for object property errors
- `['users', 0, 'email']` for nested array/object errors
- **message**: Human-readable error description
- **Context-specific properties** depending on error type:
- `expected` and `received` for type mismatches
- `minimum` and `maximum` for size constraints
- `inclusive` for whether constraints are inclusive
- `keys` for unrecognized keys
- `validation` for string format types
#### Working with Issues Directly
Access the raw issues array for maximum control:
```typescript
const result = UserSchema.safeParse(data);
if (!result.success) {
result.error.issues.forEach((issue) => {
console.log(`Error at ${issue.path.join('.')}: ${issue.message}`);
console.log(`Error code: ${issue.code}`);
if (issue.code === 'invalid_type') {
console.log(`Expected ${issue.expected}, got ${issue.received}`);
}
});
}
```
#### Error Formatting Utilities
Instead of manually processing issues, use Zod's formatting utilities:
**For flat forms** (single level):
```typescript
const flattened = z.flattenError(result.error);
flattened.formErrors;
flattened.fieldErrors.username;
flattened.fieldErrors.email;
```
**For nested structures**:
```typescript
const tree = z.treeifyError(result.error);
tree.errors;
tree.properties?.username?.errors;
tree.properties?.favoriteNumbers?.items?.[1]?.errors;
```
**For debugging**:
```typescript
const pretty = z.prettifyError(result.error);
console.log(pretty);
```
#### Common Error Response Patterns
**API Response with Issues**:
```typescript
export const handleValidationError = (error: z.ZodError) => {
return {
success: false,
errors: error.issues.map((issue) => ({
field: issue.path.join('.'),
message: issue.message,
code: issue.code,
})),
};
};
```
**Form Field Errors**:
```typescript
export const getFieldErrors = (error: z.ZodError) => {
const formatted = z.flattenError(error);
return {
formErrors: formatted.formErrors,
fieldErrors: formatted.fieldErrors,
};
};Related in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.