colinhacks/zod · error · Error

External $ref is not supported, only local refs (#/...) are

Error message

External $ref is not supported, only local refs (#/...) are allowed

What it means

Thrown by resolveRef in the v4 JSON-Schema-to-Zod converter when a `$ref` does not start with `#`. Only local (same-document) refs are supported; the converter has no mechanism to fetch or embed external schema documents.

Source

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

  const $schema = schema.$schema;

  if ($schema === "https://json-schema.org/draft/2020-12/schema") {
    return "draft-2020-12";
  }
  if ($schema === "http://json-schema.org/draft-07/schema#") {
    return "draft-7";
  }
  if ($schema === "http://json-schema.org/draft-04/schema#") {
    return "draft-4";
  }

  // Use defaultTarget if provided, otherwise default to draft-2020-12
  return defaultTarget ?? "draft-2020-12";
}

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]!;
  }

View on GitHub (pinned to 912f0f51b0)

Solutions

  1. Inline external definitions into a single `$defs` (draft 2020-12) or `definitions` (draft-07) block and rewrite refs to `#/$defs/Name`.
  2. Use a JSON Schema bundler (e.g. @apidevtools/json-schema-ref-parser) to resolve external refs before passing to fromJSONSchema.
  3. Drop or replace external refs with locally-defined equivalents if the external schema is small.

Example fix

// before
{ "$ref": "https://example.com/user.json" }

// after
{
  "$defs": { "user": { "type": "object", ... } },
  "$ref": "#/$defs/user"
}
Defensive patterns

Strategy: validation

Validate before calling

function assertLocalRefs(root: any, path = "") {
  const visit = (node: any) => {
    if (!node || typeof node !== "object") return;
    if (typeof node.$ref === "string" && !node.$ref.startsWith("#")) {
      throw new Error(`External ref at ${path}: ${node.$ref}`);
    }
    for (const k of Object.keys(node)) visit(node[k]);
  };
  visit(root);
}

Type guard

function isLocalRef(ref: string): boolean {
  return typeof ref === "string" && ref.startsWith("#");
}

Prevention

When it happens

Trigger: Passing a JSON Schema whose `$ref` points at another file or URL, e.g. `{ "$ref": "./user.json" }`, `{ "$ref": "https://example.com/schemas/user" }`, or `{ "$ref": "defs.json#/User" }`.

Common situations: Loading OpenAPI/JSON Schema bundles that split definitions across files; consuming third-party schemas that reference a shared registry by URL; bundlers that do not inline external refs.

Related errors


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