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

4.1 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Choose refine() vs superRefine() Correctly MEDIUM refine() only reports one error; superRefine() enables multiple issues and custom error codes 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):

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

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

// 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():

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