Files
gruperly/.agents/skills/zod/references/refine-vs-superrefine.md
2026-09-04 16:49:24 -03:00

153 lines
4.1 KiB
Markdown

---
title: Choose refine() vs superRefine() Correctly
impact: MEDIUM
impactDescription: refine() only reports one error; superRefine() enables multiple issues and custom error codes
tags: refine, superRefine, validation, custom
---
## Choose refine() vs superRefine() Correctly
`.refine()` is for simple single-condition validation returning boolean. `.superRefine()` gives you a context object to add multiple issues with custom error codes and paths. Choose based on your error reporting needs.
**Incorrect (using refine for multiple checks):**
```typescript
import { z } from 'zod'
// refine can only report one error at a time
const passwordSchema = z.string().refine(
(password) => {
// Checks all conditions but only reports first failure
if (password.length < 8) return false // Only this error shown
if (!/[A-Z]/.test(password)) return false
if (!/[0-9]/.test(password)) return false
return true
},
{ message: 'Password does not meet requirements' }
)
passwordSchema.parse('weak')
// Only shows: "Password does not meet requirements"
// User doesn't know WHICH requirements failed
```
**Correct (using superRefine for multiple issues):**
```typescript
import { z } from 'zod'
const passwordSchema = z.string().superRefine((password, ctx) => {
if (password.length < 8) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Password must be at least 8 characters',
})
}
if (!/[A-Z]/.test(password)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Password must contain an uppercase letter',
})
}
if (!/[0-9]/.test(password)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Password must contain a number',
})
}
if (!/[!@#$%^&*]/.test(password)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: 'Password must contain a special character',
})
}
})
passwordSchema.safeParse('weak')
// Shows ALL failures:
// - "Password must be at least 8 characters"
// - "Password must contain an uppercase letter"
// - "Password must contain a number"
// - "Password must contain a special character"
```
**When to use refine():**
```typescript
// Simple boolean condition with one error message
const adultSchema = z.number().refine(
(age) => age >= 18,
{ message: 'Must be 18 or older' }
)
// Cross-field validation with single outcome
const formSchema = z.object({
password: z.string(),
confirmPassword: z.string(),
}).refine(
(data) => data.password === data.confirmPassword,
{ message: 'Passwords must match', path: ['confirmPassword'] }
)
// Async validation
const emailSchema = z.string().email().refine(
async (email) => {
const exists = await checkEmailExists(email)
return !exists
},
{ message: 'Email already registered' }
)
```
**When to use superRefine():**
```typescript
// Multiple independent checks on same value
// Cross-field validation with multiple possible errors
// Need custom error codes for i18n or client handling
// Need to add issues at specific paths
const orderSchema = z.object({
items: z.array(z.object({
productId: z.string(),
quantity: z.number(),
})),
promoCode: z.string().optional(),
}).superRefine(async (order, ctx) => {
// Check each item's availability
for (let i = 0; i < order.items.length; i++) {
const item = order.items[i]
const available = await checkInventory(item.productId, item.quantity)
if (!available) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['items', i, 'quantity'], // Specific path
message: `Only ${available} units available`,
})
}
}
// Validate promo code
if (order.promoCode) {
const valid = await validatePromoCode(order.promoCode)
if (!valid) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
path: ['promoCode'],
message: 'Invalid or expired promo code',
})
}
}
})
```
**When NOT to use this pattern:**
- Simple single-condition checks (use refine for simplicity)
- Transform needed instead of validation (use transform)
Reference: [Zod API - refine/superRefine](https://zod.dev/api#refine)