colinhacks/zod · error · Error

Async refinement encountered during synchronous parse…

Error message

Async refinement encountered during synchronous parse operation. Use .parseAsync instead.

What it means

Thrown at packages/zod/src/v3/types.ts:4380 during a synchronous parse when a refinement's return value is a Promise. Zod detects (result instanceof Promise) inside the sync branch of the refinement effect and aborts, because awaiting that Promise would require the async parser. The error message names the exact escape hatch: switch to .parseAsync().

Solutions

  1. Switch every consumer of the schema to `await schema.parseAsync(data)` / `await schema.safeParseAsync(data)`.
  2. If a consumer must stay sync, rewrite the refinement to be synchronous (precompute the lookup, or cache the result).
  3. Split the schema: keep a sync schema for fast checks and run the async refinement only where you can await.
  4. Audit all .refine/.superRefine callbacks on the schema for a returned Promise.

Example fix

// before
const schema = z.object({
  email: z.string().refine(async (v) => !(await isBlacklisted(v))),
});
schema.parse(input); // throws: Async refinement encountered...

// after
await schema.parseAsync(input);
Defensive patterns

Strategy: validation

Validate before calling

import { ZodType, ZodFirstPartyTypeKind } from 'zod';

function findAsyncRefinements(schema: ZodType, path: string[] = []): string[] {
  const def = (schema as any)._def;
  if (!def) return [];
  if (def.typeName === ZodFirstPartyTypeKind.ZodEffects && def.effect?.type === 'refinement') {
    const src = def.effect.refinement?.toString() ?? '';
    if (/\basync\b/.test(src)) return [path.join('.') || '(root)'];
    return findAsyncRefinements(def.schema, path);
  }
  return [];
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Defining `z.string().refine(async (v) => await isValid(v))` and then parsing with `.parse()` or `.safeParse()` (the sync path). The refine callback returns a Promise, the sync branch in executeRefinement sees it, and throws.

Common situations: Adding a DB/HTTP-backed refinement to a schema that callers parse synchronously; mixing sync and async refinements in one schema; refactoring a refinement from sync to async without updating call sites; shared schema modules used by both sync and async callers.

Related errors


AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11). Data as JSON: /api/errors/935607010faa81ec. Report an issue: GitHub.

Appendix: source

Thrown at packages/zod/src/v3/types.ts:4380

        const result = this._def.schema._parseSync({
          data: processed,
          path: ctx.path,
          parent: ctx,
        });
        if (result.status === "aborted") return INVALID;
        if (result.status === "dirty") return DIRTY(result.value);
        if (status.value === "dirty") return DIRTY(result.value);
        return result;
      }
    }
    if (effect.type === "refinement") {
      const executeRefinement = (acc: unknown): any => {
        const result = effect.refinement(acc, checkCtx);
        if (ctx.common.async) {
          return Promise.resolve(result);
        }
        if (result instanceof Promise) {
          throw new Error("Async refinement encountered during synchronous parse operation. Use .parseAsync instead.");
        }
        return acc;
      };

      if (ctx.common.async === false) {
        const inner = this._def.schema._parseSync({
          data: ctx.data,
          path: ctx.path,
          parent: ctx,
        });
        if (inner.status === "aborted") return INVALID;
        if (inner.status === "dirty") status.dirty();

        // return value is ignored
        executeRefinement(inner.value);
        return { status: status.value, value: inner.value };
      } else {
        return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((inner) => {

View on GitHub (pinned to 2d90846af9)