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 fromJSONSchema converter (packages/zod/src/v4/classic/from-json-schema.ts:124) when a schema's $ref does not begin with '#'. The converter only understands local JSON-pointer references (e.g. #/$defs/Foo) because Zod schemas live in-process; any external URL/file reference has no resolvable target and is rejected up front.
Solutions
- Pre-process the schema with a bundler/dereferencer (e.g. @apidevools/json-schema-ref-parser, swagger-cli bundle) so all $refs become local #/ pointers before calling fromJSONSchema.
- Inline external schemas manually into $defs (draft-2020-12) or definitions (draft-7) and rewrite their $refs to #/$defs/Name.
- Drop or replace external $refs that have no local equivalent with a concrete inline schema.
- Verify each $ref in the document starts with '#'; log any that don't before conversion.
Example fix
// before
const schema = {
type: 'object',
properties: { user: { $ref: 'https://example.com/user.json' } },
};
fromJSONSchema(schema); // throws: External $ref is not supported
// after — inline the external schema locally
const schema = {
$defs: { User: { type: 'object', properties: { id: { type: 'string' } } } },
type: 'object',
properties: { user: { $ref: '#/$defs/User' } },
};
fromJSONSchema(schema); Defensive patterns
Strategy: validation
Validate before calling
function assertAllRefsLocal(root: unknown): string[] {
const external: 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('#')) external.push(o.$ref);
for (const v of Object.values(o)) visit(v);
};
visit(root);
return external;
}
// usage:
// const bad = assertAllRefsLocal(schema);
// if (bad.length) throw new Error(`External $refs found: ${bad.join(', ')}`); Type guard
function isLocalRef(ref: unknown): ref is `#${string}` {
return typeof ref === 'string' && ref.startsWith('#');
} Try / catch
null
Prevention
- Run a bundler/dereferencer (@apidevools/json-schema-ref-parser, swagger-cli bundle) on multi-file specs before conversion.
- Inline external schemas into $defs and rewrite refs to #/$defs/Name.
- Reject documents with non-local $refs at ingestion time.
- Keep a checklist of supported JSON Schema features when accepting third-party specs.
When it happens
Trigger: Calling fromJSONSchema on a JSON Schema document whose $ref values point to external resources, e.g. `{ "$ref": "https://example.com/schemas/user.json" }` or `{ "$ref": "user.json" }` or `{ "$ref": "urn:uuid:..." }` — anything that is not a #-prefixed local pointer.
Common situations: Consuming OpenAPI/Swagger specs that split schemas across files via $ref; importing a multi-file JSON Schema bundle without first inlining/compiling it; tooling (e.g. @apidevtools/swagger-parser) not run to dereference; drafts using $id-based cross-document refs.
Related errors
- Reference not found
- Circular reference not resolved
- Conditional schemas (if/then/else) are not supported
- Cycle detected: #/ / Set the `cycles` parameter to `"ref"`…
- dependentSchemas and dependentRequired are not supported
AI-assisted analysis of colinhacks/zod@2d90846af9 (2026-08-11).
Data as JSON: /api/errors/fa5bef923e27c0c9.
Report an issue: GitHub.
Appendix: source
Thrown at packages/zod/src/v4/classic/from-json-schema.ts:124
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 2d90846af9)