colinhacks/zod · error · Error

Reference not found

Error message

Reference not found: ${ref}

What it means

Thrown by resolveRef (packages/zod/src/v4/classic/from-json-schema.ts:139) when a local $ref correctly points into $defs/definitions (path[0] === defsKey) but the named key does not exist or is empty. The converter walks the path, finds the defs bucket for the detected draft, looks up the key in ctx.defs, and rejects when the lookup misses — meaning the document references a definition it never declared.

Solutions

  1. Check that every name referenced by #/$defs/Name (or #/definitions/Name) exists in the document's $defs/definitions object.
  2. Confirm the document's $schema matches the bucket name used by refs: draft-2020-12 uses $defs, draft-7 uses definitions — the converter picks the bucket based on $schema.
  3. Run the document through a JSON Schema validator/IDE to flag dangling references before conversion.
  4. Add the missing definition or rewrite the ref to point at an existing one.

Example fix

// before
const schema = {
  $schema: 'https://json-schema.org/draft/2020-12/schema',
  $ref: '#/$defs/User',
  $defs: { Account: { type: 'object' } }, // 'User' missing
};

// after
const schema = {
  $schema: 'https://json-schema.org/draft/2020-12/schema',
  $ref: '#/$defs/User',
  $defs: { User: { type: 'object', properties: { id: { type: 'string' } } } },
};
Defensive patterns

Strategy: validation

Validate before calling

function collectDefinedNames(root: any, version: 'draft-2020-12' | 'draft-7'): Set<string> {
  const defsKey = version === 'draft-2020-12' ? '$defs' : 'definitions';
  return new Set(Object.keys(root?.[defsKey] ?? {}));
}

function assertRefsResolve(root: any, version: 'draft-2020-12' | 'draft-7') {
  const names = collectDefinedNames(root, version);
  const defsKey = version === 'draft-2020-12' ? '$defs' : 'definitions';
  const dangling: string[] = [];
  const visit = (node: unknown) => {
    if (Array.isArray(node)) return node.forEach(visit);
    if (!node || typeof node !== 'object') return;
    const o = node as Record<string, unknown>;
    if (typeof o.$ref === 'string' && o.$ref.startsWith(`#/${defsKey}/`)) {
      const name = o.$ref.split('/')[2];
      if (!names.has(name!)) dangling.push(o.$ref);
    }
    for (const v of Object.values(o)) visit(v);
  };
  visit(root);
  if (dangling.length) throw new Error(`Dangling refs: ${dangling.join(', ')}`);
}

Type guard

null

Try / catch

null

Prevention

When it happens

Trigger: Calling fromJSONSchema with a schema like `{ "$ref": "#/$defs/Missing" }` or `{ "$ref": "#/definitions/Missing" }` where 'Missing' is not present in the corresponding $defs/definitions object. Also triggered when the $defs key is misspelled or uses the wrong draft's bucket name for the detected version.

Common situations: Renaming a definition without updating its references; truncating a schema document so $defs is dropped; mixing draft-7 'definitions' refs into a draft-2020-12 document (or vice-versa) so the defsKey lookup fails; typos in the definition name; copy-paste of refs from another schema.

Related errors


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

Appendix: source

Thrown at packages/zod/src/v4/classic/from-json-schema.ts:139

function resolveRef(ref: string, ctx: ConversionContext): JSONSchema.JSONSchema {
  if (!ref.startsWith("#")) {
    throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
  }

  const path = ref.slice(1).split("/").filter(Boolean);

  // Handle root reference "#"
  if (path.length === 0) {
    return ctx.rootSchema;
  }

  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";

  if (path[0] === defsKey) {
    const key = path[1];
    if (!key || !ctx.defs[key]) {
      throw new Error(`Reference not found: ${ref}`);
    }
    return ctx.defs[key]!;
  }

  throw new Error(`Reference not found: ${ref}`);
}

function convertBaseSchema(schema: JSONSchema.JSONSchema, ctx: ConversionContext): ZodType {
  // Handle unsupported features
  if (schema.not !== undefined) {
    // Special case: { not: {} } represents never
    if (typeof schema.not === "object" && Object.keys(schema.not).length === 0) {
      return z.never();
    }
    throw new Error("not is not supported in Zod (except { not: {} } for never)");
  }
  if (schema.unevaluatedItems !== undefined) {
    throw new Error("unevaluatedItems is not supported");

View on GitHub (pinned to 2d90846af9)