colinhacks/zod · error · Error

Async schemas not supported in object keys currently

Error message

Async schemas not supported in object keys currently

What it means

Thrown while parsing a `z.record(keyType, valueType)` in the branch where `keyType` exposes a finite `_zod.values` set (e.g. `z.enum([...])`, `z.literal(...)`, `z.union` of literals). Running the key schema against a candidate key returned a Promise, meaning the key schema is async. Records require synchronous key validation because they iterate keys synchronously to build the output object.

Source

Thrown at packages/zod/src/v4/core/schemas.ts:2903

        input,
        inst,
      });
      return payload;
    }

    const proms: Promise<any>[] = [];

    const values = def.keyType._zod.values;
    if (values) {
      payload.value = {};
      const recordKeys = new Set<string | symbol>();
      for (const key of values) {
        if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
          recordKeys.add(typeof key === "number" ? key.toString() : key);
          const keyResult = def.keyType._zod.run({ value: key, issues: [] }, ctx);
          if (keyResult instanceof Promise) {
            throw new Error("Async schemas not supported in object keys currently");
          }
          if (keyResult.issues.length) {
            payload.issues.push({
              code: "invalid_key",
              origin: "record",
              issues: keyResult.issues.map((iss) => util.finalizeIssue(iss, ctx, core.config())),
              input: key,
              path: [key],
              inst,
            });
            continue;
          }
          const outKey = keyResult.value as PropertyKey;
          const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);

          if (result instanceof Promise) {
            proms.push(
              result.then((result) => {

View on GitHub (pinned to 912f0f51b0)

Solutions

  1. Remove async refinements/transforms from the record's key schema; keep the key type synchronous (e.g. `z.enum([...])`, `z.string()`, `z.literal(...)`).
  2. Move the async validation to the value type or to a wrapping `.superRefine()` on the whole record (parsing the record itself async via `parseAsync`).
  3. If async key validation is genuinely required, validate keys manually before constructing the record instead of in the key schema.

Example fix

// before
const R = z.record(
  z.string().refine(async (k) => !k.includes(' ')), // async key
  z.number()
);

// after
const R = z.record(z.string(), z.number()).superRefine(async (rec, ctx) => {
  for (const k of Object.keys(rec)) {
    if (k.includes(' ')) ctx.addIssue({ code: 'custom', message: `bad key ${k}`, path: [k] });
  }
});
Defensive patterns

Strategy: type-guard

Validate before calling

function isSyncSchema(s) {
  const r = s._zod.run({ value: '__probe__', issues: [] }, undefined);
  return !(r instanceof Promise);
}
if (!isSyncSchema(keyType)) throw new Error('key schema is async; records require sync keys');

Type guard

function isSyncKeySchema(s): boolean {
  const r = s._zod?.run?.({ value: '', issues: [] });
  return r === undefined || !(r instanceof Promise);
}

Try / catch

try {
  z.record(keyType, valueType).parse(input);
} catch (e) {
  if (e instanceof Error && e.message === 'Async schemas not supported in object keys currently') {
    // make keyType sync, or move async logic to a superRefine on the record
  }
  throw e;
}

Prevention

When it happens

Trigger: Using an async key schema such as `z.string().refine(async () => ...)`, a `z.pipeline()` containing an async transform, or any schema whose `_zod.run` returns a Promise as the first argument to `z.record(keyType, valueType)`.

Common situations: Adding a `.refine(async fn)` to the record's key type for uniqueness checks; building a record from an enum piped through an async transform.

Related errors


AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03). Data as JSON: /data/errors/d67f720d670fe524.json. Report an issue: GitHub.