{"record":{"id":"fa5bef923e27c0c9","repo":"colinhacks/zod","slug":"external-ref-is-not-supported-only-local-refs","errorCode":null,"errorMessage":"External $ref is not supported, only local refs (#/...) are allowed","messagePattern":"External \\$ref is not supported, only local refs \\(#/\\.\\.\\.\\) are allowed","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/zod/src/v4/classic/from-json-schema.ts","lineNumber":124,"sourceCode":"  const $schema = schema.$schema;\n\n  if ($schema === \"https://json-schema.org/draft/2020-12/schema\") {\n    return \"draft-2020-12\";\n  }\n  if ($schema === \"http://json-schema.org/draft-07/schema#\") {\n    return \"draft-7\";\n  }\n  if ($schema === \"http://json-schema.org/draft-04/schema#\") {\n    return \"draft-4\";\n  }\n\n  // Use defaultTarget if provided, otherwise default to draft-2020-12\n  return defaultTarget ?? \"draft-2020-12\";\n}\n\nfunction resolveRef(ref: string, ctx: ConversionContext): JSONSchema.JSONSchema {\n  if (!ref.startsWith(\"#\")) {\n    throw new Error(\"External $ref is not supported, only local refs (#/...) are allowed\");\n  }\n\n  const path = ref.slice(1).split(\"/\").filter(Boolean);\n\n  // Handle root reference \"#\"\n  if (path.length === 0) {\n    return ctx.rootSchema;\n  }\n\n  const defsKey = ctx.version === \"draft-2020-12\" ? \"$defs\" : \"definitions\";\n\n  if (path[0] === defsKey) {\n    const key = path[1];\n    if (!key || !ctx.defs[key]) {\n      throw new Error(`Reference not found: ${ref}`);\n    }\n    return ctx.defs[key]!;\n  }","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/colinhacks/zod/blob/2d90846af918af9602e088812d63a035d47cdbe4/packages/zod/src/v4/classic/from-json-schema.ts#L106-L142","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst schema = {\n  type: 'object',\n  properties: { user: { $ref: 'https://example.com/user.json' } },\n};\nfromJSONSchema(schema); // throws: External $ref is not supported\n\n// after — inline the external schema locally\nconst schema = {\n  $defs: { User: { type: 'object', properties: { id: { type: 'string' } } } },\n  type: 'object',\n  properties: { user: { $ref: '#/$defs/User' } },\n};\nfromJSONSchema(schema);","handlingStrategy":"validation","validationCode":"function assertAllRefsLocal(root: unknown): string[] {\n  const external: string[] = [];\n  const visit = (node: unknown) => {\n    if (Array.isArray(node)) return node.forEach(visit);\n    if (!node || typeof node !== 'object') return;\n    const o = node as Record<string, unknown>;\n    if (typeof o.$ref === 'string' && !o.$ref.startsWith('#')) external.push(o.$ref);\n    for (const v of Object.values(o)) visit(v);\n  };\n  visit(root);\n  return external;\n}\n\n// usage:\n// const bad = assertAllRefsLocal(schema);\n// if (bad.length) throw new Error(`External $refs found: ${bad.join(', ')}`);","typeGuard":"function isLocalRef(ref: unknown): ref is `#${string}` {\n  return typeof ref === 'string' && ref.startsWith('#');\n}","tryCatchPattern":"null","preventionTips":["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."],"tags":["json-schema","ref","from-json-schema","conversion","external"],"backgroundTag":null,"analyzedSha":"2d90846af918af9602e088812d63a035d47cdbe4","analyzedAt":"2026-08-11T01:21:44.015Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-22T11:17:16.035Z"}