Files
gruperly/.agents/skills/zod/references/parse-async-for-async-refinements.md
2026-09-04 16:49:24 -03:00

3.0 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Use parseAsync for Async Refinements CRITICAL Using parse() with async refinements throws an error; async validation silently fails or crashes the application parse, async, parseAsync, refinement

Use parseAsync for Async Refinements

If your schema uses refine() or superRefine() with async validation (like database lookups), you must use parseAsync() or safeParseAsync(). Using synchronous parse() with async refinements throws an error.

Incorrect (sync parse with async refinement):

import { z } from 'zod'

const userSchema = z.object({
  email: z.string().email(),
  username: z.string().min(3),
}).refine(
  async (data) => {
    // Async database check
    const exists = await db.users.findByEmail(data.email)
    return !exists
  },
  { message: 'Email already registered' }
)

// This throws an error!
const user = userSchema.parse(formData)
// Error: Async refinement encountered during synchronous parse operation.
// Use .parseAsync instead.

Correct (using parseAsync):

import { z } from 'zod'

const userSchema = z.object({
  email: z.string().email(),
  username: z.string().min(3),
}).refine(
  async (data) => {
    const exists = await db.users.findByEmail(data.email)
    return !exists
  },
  { message: 'Email already registered' }
)

// Use parseAsync for async refinements
const user = await userSchema.parseAsync(formData)

// Or safeParseAsync for error handling
const result = await userSchema.safeParseAsync(formData)
if (!result.success) {
  console.log(result.error.issues)
}

Async transforms also require parseAsync:

const enrichedUserSchema = z.object({
  userId: z.string().uuid(),
}).transform(async (data) => {
  // Async data enrichment
  const user = await db.users.findById(data.userId)
  return {
    ...data,
    email: user.email,
    name: user.name,
  }
})

// Must use parseAsync
const enrichedUser = await enrichedUserSchema.parseAsync({ userId: '123' })

Pattern for API routes:

import { z } from 'zod'
import { NextRequest, NextResponse } from 'next/server'

const registerSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
}).superRefine(async (data, ctx) => {
  const existingUser = await db.users.findByEmail(data.email)
  if (existingUser) {
    ctx.addIssue({
      code: z.ZodIssueCode.custom,
      path: ['email'],
      message: 'Email already registered',
    })
  }
})

export async function POST(req: NextRequest) {
  const body = await req.json()

  // Always use safeParseAsync with async schemas
  const result = await registerSchema.safeParseAsync(body)

  if (!result.success) {
    return NextResponse.json({ errors: result.error.issues }, { status: 400 })
  }

  // Proceed with registration
}

When NOT to use this pattern:

  • Schemas with only synchronous validation (use parse/safeParse)
  • When async validation can be moved outside Zod (validate, then check)

Reference: Zod API - parseAsync