--- title: Use parseAsync for Async Refinements impact: CRITICAL impactDescription: Using parse() with async refinements throws an error; async validation silently fails or crashes the application tags: 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):** ```typescript 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):** ```typescript 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:** ```typescript 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:** ```typescript 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](https://zod.dev/api#parseasync)