Overview
Refinements allow you to add custom validation logic beyond Zod’s built-in validators. While transformations modify data, refinements validate data without changing it.Basic Refinement with .refine()
Use.refine() to add custom validation:
packages/zod/src/v4/classic/schemas.ts:92-95, the .refine() signature:
Custom Error Messages
Provide custom error messages:packages/zod/src/v4/classic/tests/refine.test.ts:54-75:
Refine on Objects
Refinements are powerful for cross-field validation:packages/zod/src/v4/classic/tests/refine.test.ts:169-181:
Error Path
Thepath option specifies where the error should appear:
Async Refinements
Refinements can be asynchronous for database checks, API calls, etc:packages/zod/src/v4/classic/tests/refine.test.ts:77-108:
superRefine() for Advanced Validation
For complex validation with multiple errors, use.superRefine():
packages/zod/src/v4/classic/schemas.ts:96-98, the .superRefine() signature:
packages/zod/src/v4/classic/tests/refine.test.ts:183-215:
Adding Multiple Issues
.superRefine() allows reporting multiple validation errors:
Early Termination
Control validation flow with early termination options:Using fatal: true
packages/zod/src/v4/classic/tests/refine.test.ts:133-154:
Using continue: false
Using abort in .refine()
packages/zod/src/v4/classic/tests/refine.test.ts:155-167:
Type Narrowing with Refinements
Refinements can narrow TypeScript types using type predicates:packages/zod/src/v4/classic/tests/refine.test.ts:422-432:
Practical Examples
Email Uniqueness Check
Password Confirmation
Date Range Validation
Complex Business Logic
Credit Card Validation
Refinements vs Transformations
Key Difference:
- Refinements validate data without changing it
- Transformations modify data after validation
Chaining Refinements
Multiple refinements can be chained:Performance Considerations
Best Practices
- Use built-in validators first - They’re optimized and well-tested
- Keep refinements focused - One validation per refinement
- Use .superRefine() for multiple errors - Better UX than stopping at first error
- Set appropriate error paths - Help users fix the right field
- Consider async performance - Cache or batch when possible
- Use type predicates for narrowing - Get better TypeScript types
Common Patterns
Conditional Validation
Dependent Fields
Next Steps
- Learn about Transformations to modify data during parsing
- Explore Parsing to understand error handling
- Master Type Inference for type narrowing with refinements