swagger $ref definitions components/schema
a big big bug in my swagger.json
here is my code
const sys_user_role_postDto = zType.object({
user: get_sys_userDto,
postIds: zType.array(zType.number()),
posts: zType.array(sys_deptDto),
roleIds: zType.array(zType.number()),
roles: zType.array(sys_roleDto),
}).meta({ id: 'sys_user_role_postDto' })
const sys_user_role_post_listDto = zType.array(sys_user_role_postDto)
.meta({ id: 'sys_user_role_post_listDto' })
//-------
const schema = z.toJSONSchema(z.globalRegistry, {
target: 'openapi-3.0',
uri: (id) => `#/components/schemas/${id}`,
io: "input",
unrepresentable: "any",
override: (ctx) => {
const def = ctx.zodSchema._zod.def;
if (def.type === "date") {
ctx.jsonSchema.type = "string";
ctx.jsonSchema.format = "date-time";
}
if (def.type === "bigint") {
ctx.jsonSchema.type = "number";
}
},
});
If I give up using uri, the bug is more and more
What should I do
Hi @shanliangdeYWJ! I'm Dosu and I’m helping the zod team.
I'm Dosu, a bot trained on Zod's codebase and issues.
Your Swagger/OpenAPI schema is missing definitions because referenced schemas (like captchaImageDto) are not registered with an id in the global registry, or they're not included in the registry you pass to z.toJSONSchema. Every schema you want to reference with $ref must be registered with a unique id using .meta({ id: '...' }) and added to the registry—otherwise, the output will reference missing definitions and Swagger UI will show resolver errors. This is the most common cause of the bug you're seeing source.
The uri option is correct for OpenAPI: uri: (id) => '#/components/schemas/' + id ensures $ref points to the right place. If you skip uri, $ref defaults to just the id string, which won't work for OpenAPI source.
Checklist:
- Make sure every referenced schema (like
captchaImageDto) is defined, has.meta({ id: 'captchaImageDto' }), and is registered inz.globalRegistry. - Pass the registry (not a single schema) to
z.toJSONSchema. - Use the correct
urifunction. - For best OpenAPI 3.0 compatibility, set
target: 'draft-7'instead of'openapi-3.0'—the latter is new and may have rough edges source.
If you fix the missing registrations and ids, your Swagger output should include all referenced schemas under components/schemas and resolve $ref correctly. If your question is answered, please close the issue!
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
const captchaImageDto = zType.object({
img: zType.string('<svg>.......<svg/>'),
captchaEnabled: zType.boolean('true'),
uuid: zType.string(`${121}-${121}-${121}-${121}-${121}`),
}).meta({ id: 'captchaImageDto' })
const response = = zType.object({
code: zType.number(200).meta({description: 'response code'}),
message: zType.string('success').meta({description: 'response message'}),
data: captchaImageDto
})
I first defined captchaImageDto, then used it in the return format, and then registered it globally. However, an error occurred because it had already been registered when the id was added. Therefore, a better approach is still needed
This error happens because Zod's .meta({ id: ... }) creates a new schema instance and registers it in the global registry. If you call .meta({ id: 'captchaImageDto' }) on multiple schema instances (even if the shape is identical), Zod tries to register the same id more than once, which throws an error for duplicate ids in the registry docs issue.
Best practice:
Define your schema with .meta({ id: ... }) once, and always reuse that same schema instance wherever you need it. Do not call .meta({ id: ... }) again on a new instance or inside other schemas. For example:
// Define and register once
const captchaImageDto = zType.object({
img: zType.string('<svg></svg>.......<svg></svg>'),
captchaEnabled: zType.boolean('true'),
uuid: zType.string(`${121}-${121}-${121}-${121}-${121}`),
}).meta({ id: 'captchaImageDto' });
// Reuse the same instance everywhere
const response = zType.object({
code: zType.number('响应码', 200),
message: zType.string('响应描述', '成功'),
data: captchaImageDto // <-- reuse, don't redefine
});
If you need to add a description or other metadata, use .describe() or .meta() without the id on the reused instance, but never assign the same id to multiple schema instances docs issue.
For OpenAPI/Swagger, always pass your registry (like z.globalRegistry) to z.toJSONSchema, and use the uri option to format $ref correctly docs.
If your question is answered, please close the issue!
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
"I haven't solved this problem. When I use get, it will be parsed #/definitions/
If your generated OpenAPI/Swagger schema is producing $ref paths like #/definitions/ instead of #/components/schemas/, it usually means the uri option in z.toJSONSchema isn't set correctly, or the registry isn't being passed in as expected. For OpenAPI 3.0, you should pass your registry (like z.globalRegistry) and set uri: (id) => '#/components/schemas/' + id to ensure references point to the right place docs issue.
Also, check your target option: for best compatibility with OpenAPI 3.0, use target: 'draft-7' instead of 'openapi-3.0'—the latter is newer and may have rough edges issue.
If you’re still seeing #/definitions/, please share a minimal reproducible example: your code calling z.toJSONSchema, the relevant Zod schema definitions, and the generated output (especially the $ref paths). Also, let me know your Zod version and whether your Swagger tool expects OpenAPI 3.0 or Swagger 2.0. This will help pinpoint the issue!
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
I use z.toJsonSchema to parse schema
const sys_menuDto = z.object({
menuId: z.number(),
}).meta({ id: 'sys_menuDto' })
const sys_menu_listDto = z.array(sys_menuDto).meta({ id: 'sys_menu_listDto' })
const schema = z.toJSONSchema(sys_menu_listDto, {
target: 'openapi-3.0',
// uri: (id) => `#/components/schemas/${id}`,
io: "input",
unrepresentable: "any",
override: (ctx) => {
const def = ctx.zodSchema._zod.def;
if (def.type === "date") {
ctx.jsonSchema.type = "string";
ctx.jsonSchema.format = "date-time";
}
if (def.type === "bigint") {
ctx.jsonSchema.type = "number";
}
},
})
console.log("schema");
console.dir(schema, { depth: null })
/*
{
id: 'sys_menu_listDto',
type: 'array',
items: { '$ref': '#/definitions/sys_menuDto' },
definitions: {
sys_menuDto: {
id: 'sys_menuDto',
type: 'object',
properties: { menuId: { type: 'number' } },
required: [ 'menuId' ]
}
}
}
*/
the ref will be '$ref': '#/definitions/sys_menuDto' , I hope he can customize it #/components/schemas
The reason your $ref is #/definitions/sys_menuDto is because you're passing a single schema to z.toJSONSchema. To customize $ref paths (for example, to #/components/schemas/sys_menuDto for OpenAPI 3.0), you need to pass a registry containing your schemas and set the uri option. The uri function lets you control how $ref paths are generated docs.
Here's how you can do it:
const sys_menuDto = z.object({
menuId: z.number(),
}).meta({ id: 'sys_menuDto' });
const sys_menu_listDto = z.array(sys_menuDto).meta({ id: 'sys_menu_listDto' });
// Register schemas in the global registry (or create your own registry)
z.globalRegistry.add(sys_menuDto, { id: 'sys_menuDto' });
z.globalRegistry.add(sys_menu_listDto, { id: 'sys_menu_listDto' });
// Pass the registry and set the uri option
const schema = z.toJSONSchema(z.globalRegistry, {
target: 'openapi-3.0',
uri: (id) => `#/components/schemas/${id}`,
io: "input",
unrepresentable: "any",
override: (ctx) => {
const def = ctx.zodSchema._zod.def;
if (def.type === "date") {
ctx.jsonSchema.type = "string";
ctx.jsonSchema.format = "date-time";
}
if (def.type === "bigint") {
ctx.jsonSchema.type = "number";
}
},
});
console.dir(schema, { depth: null });
This will generate $ref paths like #/components/schemas/sys_menuDto instead of #/definitions/sys_menuDto docs.
Key points:
- Always pass a registry (not a single schema) to
z.toJSONSchemafor interlinked schemas and custom$refpaths. - All schemas you want referenced must have a unique
idand be registered. - The
urioption controls the$refformat.
If you still see #/definitions/, double-check that you're passing the registry and not a single schema. If your question is answered, please close the issue!
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
You are using global registration, whereas I need to perform subsequent operations on the JSON generated by a single schema to generate the Swagger on the specified path. Global registration cannot obtain individual information
I'm Dosu, a bot trained on Zod's codebase and issues.
Customizing $ref paths (like #/components/schemas/...) when exporting JSON Schema from a single Zod schema is not supported—this feature only works when you pass a registry (global or custom) to z.toJSONSchema and set the uri option. When you export just a single schema, Zod always outputs a self-contained schema without $ref customization or inter-schema linking, so the uri option has no effect in that case (docs, issue, issue).
If you want to avoid global registration, you can create your own custom registry, register only the schemas you need, and then pass that registry to z.toJSONSchema with your preferred uri function. This gives you full control over which schemas are included and how $ref paths are generated, without polluting global state (docs).
If you absolutely need to generate OpenAPI-compatible JSON from a single schema and customize $ref paths, the only workaround is to use the internal JSONSchemaGenerator class from zod/v4/core, but this is not part of the public API and may break in the future (workaround example). There is currently no public API for customizing $ref paths in single-schema export, but you can track or request this feature in the Zod repo.
Let me know if you want a concrete example of using a custom registry for per-endpoint Swagger generation! If this answers your question, please close the issue.
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
Does that mean Zod doesn't have the capability to safely generate individual schemas for Swagger path parameters or request body parameters?
Zod can safely generate JSON Schemas for Swagger path parameters or request body parameters, but with an important caveat: if you want $ref references (like #/components/schemas/...) and full OpenAPI compatibility, you must use a registry (either global or custom) and pass it to z.toJSONSchema with the uri option. This is the only supported way to customize $ref paths and generate interlinked schemas for Swagger/OpenAPI docs issue.
If you export a single schema (not a registry), Zod always outputs a self-contained schema—no $ref customization or inter-schema linking is possible, so the uri option has no effect docs issue.
If you want to avoid global registration, you can create a custom registry, register only the schemas you need, and then generate your Swagger/OpenAPI components from that registry. This gives you per-endpoint control without polluting global state docs.
There is a workaround using the internal JSONSchemaGenerator class from zod/v4/core to customize $ref URIs for single schemas, but this is not part of the public API and may break in the future issue.
If you need an example of using a custom registry for per-endpoint Swagger generation, let me know! If this answers your question, please close the issue.
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
Zod can safely generate JSON Schemas for Swagger path parameters or request body parameters, but with an important caveat: if you want $ref references (like
#/components/schemas/...) and full OpenAPI compatibility, you must use a registry (either global or custom) and pass it toz.toJSONSchemawith theurioption. This is the only supported way to customize $ref paths and generate interlinked schemas for Swagger/OpenAPI docs issue.If you export a single schema (not a registry), Zod always outputs a self-contained schema—no $ref customization or inter-schema linking is possible, so the
urioption has no effect docs issue.If you want to avoid global registration, you can create a custom registry, register only the schemas you need, and then generate your Swagger/OpenAPI components from that registry. This gives you per-endpoint control without polluting global state docs.
There is a workaround using the internal
JSONSchemaGeneratorclass fromzod/v4/coreto customize $ref URIs for single schemas, but this is not part of the public API and may break in the future issue.If you need an example of using a custom registry for per-endpoint Swagger generation, let me know! If this answers your question, please close the issue.
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
![]()
![]()
I need an example of using a custom registry for per-endpoint Swagger generation. @dosu._
Here’s a concrete example of using a custom Zod registry for per-endpoint Swagger/OpenAPI schema generation with custom $ref paths—no global registration required:
import { z } from "zod";
// 1. Create a custom registry for this endpoint
const registry = z.registry<{ id: string }>();
// 2. Define your schemas and register them with unique ids
const UserDto = z.object({
id: z.number(),
name: z.string(),
}).register(registry, { id: "UserDto" });
const UserListDto = z.array(UserDto).register(registry, { id: "UserListDto" });
// 3. Generate JSON Schema with custom $ref paths for OpenAPI
const openApiSchema = z.toJSONSchema(registry, {
target: "openapi-3.0",
uri: (id) => `#/components/schemas/${id}`,
// ...other options as needed
});
console.dir(openApiSchema, { depth: null });
This will produce JSON Schema where $ref paths look like #/components/schemas/UserDto, and only the schemas you registered in registry are included—no global state pollution. You can repeat this pattern for each endpoint or group of endpoints as needed.
Key points:
- Always assign a unique
idper schema in the registry. - Reuse the same schema instance when referencing it elsewhere to avoid duplicate id errors.
- The
urioption controls the$refformat for OpenAPI compatibility. - Exporting a single schema (not a registry) will not generate
$reflinks—registries are required for interlinked schemas and $ref customization.
References: Zod docs: Metadata & Registries, JSON Schema generation, GitHub issue with example.
If this solves your problem, please close the issue!
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other
This solution is terrible. For the business, what is needed is a finished solution. This one is not encapsulated into a function, and it requires passing in a name. I don't want to pass in the name myself, except for meta, because I don't want to remember these complicated things, and I can't produce a good finished product. I hope to have a better encapsulated function@dosu
Zod does not currently provide a fully encapsulated, business-friendly function that automatically handles id assignment and registry management for Swagger/OpenAPI schema generation. You still need to assign unique ids (via .meta({ id }) or .register(registry, { id })) and manage registries yourself to get correct $ref paths and interlinked schemas for OpenAPI docs json-schema.
There is no built-in utility that abstracts away these steps or auto-generates ids for you. This is a common feature request, and the maintainers have confirmed that the only supported way is to use registries and manual id assignment issue. Some users have built their own wrappers or use community tools like zod-to-json-schema for more automation, but these are not part of core Zod.
If you want a more "finished" solution, you could write a helper that auto-generates ids (e.g., from variable names or file paths) and registers schemas behind the scenes, but this would be custom code outside of Zod's official API.
If you want to see this improved in Zod, you can follow or upvote the related feature requests in the repo. If you have more specific requirements, let me know and I can help sketch a custom helper pattern!
To reply, just mention @dosu.
How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other