--- 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)