colinhacks/zod · error · Error
Schema is missing an `id` property
Error message
Schema is missing an `id` property
What it means
Thrown near the end of `z.toJSONSchema()` when an `external.uri` generator is configured but the root schema being converted has no registered `id`. The external-URI feature constructs the output `$id` from the schema's id; without one it cannot form the URI. The schema must be registered with an id (via `.meta({ id })` or an external registry) before conversion.
Source
Thrown at packages/zod/src/v4/core/to-json-schema.ts:468
flattenRef(entry[0]);
}
const result: JSONSchema.BaseSchema = {};
if (ctx.target === "draft-2020-12") {
result.$schema = "https://json-schema.org/draft/2020-12/schema";
} else if (ctx.target === "draft-07") {
result.$schema = "http://json-schema.org/draft-07/schema#";
} else if (ctx.target === "draft-04") {
result.$schema = "http://json-schema.org/draft-04/schema#";
} else if (ctx.target === "openapi-3.0") {
// OpenAPI 3.0 schema objects should not include a $schema property
} else {
// Arbitrary string values are allowed but won't have a $schema property set
}
if (ctx.external?.uri) {
const id = ctx.external.registry.get(schema)?.id;
if (!id) throw new Error("Schema is missing an `id` property");
result.$id = ctx.external.uri(id);
}
Object.assign(result, root.def ?? root.schema);
// The `id` in `.meta()` is a Zod-specific registration tag used to extract
// schemas into $defs — it is not user-facing JSON Schema metadata. Strip it
// from the output body where it would otherwise leak. The id is preserved
// implicitly via the $defs key (and via $ref paths).
const rootMetaId = ctx.metadataRegistry.get(schema)?.id;
if (rootMetaId !== undefined && result.id === rootMetaId) delete result.id;
// build defs object
const defs: JSONSchema.BaseSchema["$defs"] = ctx.external?.defs ?? {};
for (const entry of ctx.seen.entries()) {
const seen = entry[1];
if (seen.def && seen.defId) {
if (seen.def.id === seen.defId) delete seen.def.id;View on GitHub (pinned to 912f0f51b0)
Solutions
- Add an id to the schema: `schema.meta({ id: 'MyType' })` before converting.
- Register the schema in the external registry with an id: `registry.add(schema, { id: 'MyType' })`.
- Drop the `external.uri` option if you do not need cross-document `$id` emission.
Example fix
// before
const S = z.object({ a: z.string() });
z.toJSONSchema(S, { external: { uri: (id) => `https://x.com/${id}.json`, registry: new Map() } }); // throws
// after
const S = z.object({ a: z.string() }).meta({ id: 'Thing' });
z.toJSONSchema(S, { external: { uri: (id) => `https://x.com/${id}.json`, registry: new Map() } }); Defensive patterns
Strategy: validation
Validate before calling
function ensureHasId(schema) {
const id = (schema as any)._zod?.def?.meta?.id;
if (!id) throw new Error('schema needs .meta({ id }) before external URI conversion');
} Type guard
function schemaHasId(s, registry): boolean {
return !!(registry?.get(s)?.id ?? (s as any)._zod?.def?.meta?.id);
} Try / catch
try {
z.toJSONSchema(schema, { external: { uri, registry } });
} catch (e) {
if (e instanceof Error && e.message === 'Schema is missing an `id` property') {
// add schema.meta({ id: '...' }) or registry.add(schema, { id }) and retry
}
throw e;
} Prevention
- Always tag root schemas with .meta({ id }) when using external URI generation.
- Register schemas in the external registry with an id at registration time.
- Drop the external.uri option if you do not need cross-document $id emission.
When it happens
Trigger: Calling `z.toJSONSchema(schema, { external: { uri: (id) => `https://x.com/${id}.json`, registry } })` where `schema` has no id in the registry and no `.meta({ id })`.
Common situations: Setting up cross-document `$ref`s with an external registry but forgetting to tag the root schema; renaming a schema and dropping its id.
Related errors
- Duplicate schema id "${id}" detected during JSON Schema conv
- [toJSONSchema]: Non-representable type encountered: ${def.ty
- Cycle detected: #/${seen.cycle?.join("/")}/<root> Set the `
- Error converting schema to JSON.
- External $ref is not supported, only local refs (#/...) are
AI-assisted analysis of colinhacks/zod@912f0f51b0 (2026-08-03).
Data as JSON: /data/errors/e5daad3e436f0113.json.
Report an issue: GitHub.