Gathering detailed insights and metrics for zod-openapi
Gathering detailed insights and metrics for zod-openapi
Gathering detailed insights and metrics for zod-openapi
Gathering detailed insights and metrics for zod-openapi
@asteasolutions/zod-to-openapi
Builds OpenAPI schemas from Zod schemas
@anatine/zod-openapi
Zod to OpenAPI converter
@hono/zod-openapi
A wrapper class of Hono which supports OpenAPI.
openapi-zod-client
[](https://openapi-zod-client.vercel.app/)
Use Zod Schemas to create OpenAPI v3.1x documentation
npm install zod-openapi
Typescript
Module System
Min. Node Version
Node Version
NPM Version
99.4
Supply Chain
100
Quality
95
Maintenance
100
Vulnerability
100
License
TypeScript (99.35%)
JavaScript (0.65%)
Total Downloads
3,858,846
Last Day
6,204
Last Week
149,686
Last Month
615,615
Last Year
3,431,453
MIT License
446 Stars
564 Commits
23 Forks
4 Watchers
6 Branches
11 Contributors
Updated on Jul 06, 2025
Minified
Minified + Gzipped
Latest Version
5.0.0
Package Id
zod-openapi@5.0.0
Unpacked Size
148.25 kB
Size
26.23 kB
File Count
17
NPM Version
10.9.2
Node Version
22.16.0
Published on
Jul 05, 2025
Cumulative downloads
Total Downloads
Last Day
-21.5%
6,204
Compared to previous day
Last Week
-10.9%
149,686
Compared to previous week
Last Month
31%
615,615
Compared to previous month
Last Year
718.8%
3,431,453
Compared to previous year
1
A TypeScript library which uses Zod schemas to generate OpenAPI v3.1x documentation.
Install via npm
, yarn
, or pnpm
:
1npm install zod zod-openapi 2# or 3yarn add zod zod-openapi 4# or 5pnpm install zod zod-openapi
.meta()
Use the .meta()
method to add OpenAPI metadata to a Zod schema. It accepts an object with the following options:
Option | Description |
---|---|
id | Registers a schema as a reusable OpenAPI component. |
header | Adds metadata for response headers. |
param | Adds metadata for request parameters. |
override | Allows you to override the rendered OpenAPI schema. This takes either an object or a function. |
outputId | Allows you to set a different ID for the output schema. This is useful when the input and output schemas differ. |
unusedIO | Allows you to set the io for an unused schema added to the components section. Defaults to output . |
You can also set standard OpenAPI properties directly in the .meta()
method, such as:
1z.string().meta({ 2 description: 'A text field', 3 example: 'Example value', 4});
createDocument
Generates an OpenAPI documentation object.
1import * as z from 'zod/v4'; 2import { createDocument } from 'zod-openapi'; 3 4const jobId = z.string().meta({ 5 description: 'A unique identifier for a job', 6 example: '12345', 7 id: 'jobId', 8}); 9 10const title = z.string().meta({ 11 description: 'Job title', 12 example: 'My job', 13}); 14 15const document = createDocument({ 16 openapi: '3.1.0', 17 info: { 18 title: 'My API', 19 version: '1.0.0', 20 }, 21 paths: { 22 '/jobs/{jobId}': { 23 put: { 24 requestParams: { path: z.object({ jobId }) }, 25 requestBody: { 26 content: { 27 'application/json': { schema: z.object({ title }) }, 28 }, 29 }, 30 responses: { 31 '200': { 32 description: '200 OK', 33 content: { 34 'application/json': { schema: z.object({ jobId, title }) }, 35 }, 36 }, 37 }, 38 }, 39 }, 40 }, 41});
1{ 2 "openapi": "3.1.0", 3 "info": { 4 "title": "My API", 5 "version": "1.0.0" 6 }, 7 "paths": { 8 "/jobs/{jobId}": { 9 "put": { 10 "parameters": [ 11 { 12 "in": "path", 13 "name": "jobId", 14 "description": "A unique identifier for a job", 15 "schema": { 16 "$ref": "#/components/schemas/jobId" 17 } 18 } 19 ], 20 "requestBody": { 21 "content": { 22 "application/json": { 23 "schema": { 24 "type": "object", 25 "properties": { 26 "title": { 27 "type": "string", 28 "description": "Job title", 29 "example": "My job" 30 } 31 }, 32 "required": ["title"] 33 } 34 } 35 } 36 }, 37 "responses": { 38 "200": { 39 "description": "200 OK", 40 "content": { 41 "application/json": { 42 "schema": { 43 "type": "object", 44 "properties": { 45 "jobId": { 46 "$ref": "#/components/schemas/jobId" 47 }, 48 "title": { 49 "type": "string", 50 "description": "Job title", 51 "example": "My job" 52 } 53 }, 54 "required": ["jobId", "title"] 55 } 56 } 57 } 58 } 59 } 60 } 61 } 62 }, 63 "components": { 64 "schemas": { 65 "jobId": { 66 "type": "string", 67 "description": "A unique identifier for a job", 68 "example": "12345" 69 } 70 } 71 } 72}
createDocument
takes an optional options argument which can be used to modify how the document is created
1createDocument(doc, { 2 override: ({ jsonSchema, zodSchema, io }) => { 3 // Customize the schema generation 4 if (io === 'output') { 5 jsonSchema.type = 'string'; 6 } 7 }, 8});
Option | Type | Default | Description |
---|---|---|---|
override | Function | undefined | Override rendered schema with a function`` |
outputIdSuffix | string | 'Output' | Suffix for output schema IDs when the schema is used in both a request and response |
allowEmptySchema | Object | undefined | Control whether empty schemas are allowed. |
cycles | 'ref' | 'throw' | 'ref' | How to handle cycles in schemas. - 'ref' — Break cycles using $defs- 'throw' — Error on cycles |
reused | 'ref' | 'inline' | 'inline' | How to handle reused schemas. - 'ref' — Reused schemas as references- 'inline' — Inline reused schemas |
createSchema
Creates an OpenAPI Schema Object along with any registered components. OpenAPI 3.1.0 Schema Objects are fully compatible with JSON Schema.
1import * as z from 'zod/v4'; 2import { createSchema } from 'zod-openapi'; 3 4const jobId = z.string().meta({ 5 description: 'A unique identifier for a job', 6 example: '12345', 7 id: 'jobId', 8}); 9 10const title = z.string().meta({ 11 description: 'Job title', 12 example: 'My job', 13}); 14 15const job = z.object({ 16 jobId, 17 title, 18}); 19 20const { schema, components } = createSchema(job);
1{ 2 "schema": { 3 "type": "object", 4 "properties": { 5 "jobId": { 6 "$ref": "#/components/schemas/jobId" 7 }, 8 "title": { 9 "type": "string", 10 "description": "Job title", 11 "example": "My job" 12 } 13 }, 14 "required": ["jobId", "title"] 15 }, 16 "components": { 17 "jobId": { 18 "type": "string", 19 "description": "A unique identifier for a job", 20 "example": "12345" 21 } 22 } 23}
createSchema
takes an optional CreateSchemaOptions
parameter which includes all options from CreateDocumentOptions plus the following:
1const { schema, components } = createSchema(job, { 2 // Input/Output context - controls how schemas are generated 3 io: 'input', // 'input' for request bodies/params, 'output' for responses 4 // Component handling 5 schemaComponents: { jobId: z.string() }, // Pre-defined components to use 6 schemaComponentRefPath: '#/definitions/', // Custom path prefix for component references 7 opts: {}, // Create Document Options, 8});
Query, Path, Header & Cookie parameters can be created using the requestParams
key under the method
key as follows:
1createDocument({ 2 paths: { 3 '/jobs/{a}': { 4 put: { 5 requestParams: { 6 path: z.object({ a: z.string() }), 7 query: z.object({ b: z.string() }), 8 cookie: z.object({ cookie: z.string() }), 9 header: z.object({ 'custom-header': z.string() }), 10 }, 11 }, 12 }, 13 }, 14});
If you would like to declare parameters in a more traditional way you may also declare them using the parameters key. The definitions will then all be combined.
1createDocument({ 2 paths: { 3 '/jobs/{a}': { 4 put: { 5 parameters: [ 6 z.string().meta({ 7 param: { 8 name: 'job-header', 9 in: 'header', 10 }, 11 }), 12 ], 13 }, 14 }, 15 }, 16});
Where you would normally declare the media type, set the schema
as your Zod Schema as follows.
1createDocument({ 2 paths: { 3 '/jobs': { 4 get: { 5 requestBody: { 6 content: { 7 'application/json': { schema: z.object({ a: z.string() }) }, 8 }, 9 }, 10 }, 11 }, 12 }, 13});
If you wish to use OpenAPI syntax for your schemas, simply add an OpenAPI schema to the schema
field instead.
Similarly to the Request Body, simply set the schema
as your Zod Schema as follows. You can set the response headers using the headers
key.
1createDocument({ 2 paths: { 3 '/jobs': { 4 get: { 5 responses: { 6 200: { 7 description: '200 OK', 8 content: { 9 'application/json': { schema: z.object({ a: z.string() }) }, 10 }, 11 headers: z.object({ 12 'header-key': z.string(), 13 }), 14 }, 15 }, 16 }, 17 }, 18 }, 19});
1createDocument({ 2 paths: { 3 '/jobs': { 4 get: { 5 callbacks: { 6 onData: { 7 '{$request.query.callbackUrl}/data': { 8 post: { 9 requestBody: { 10 content: { 11 'application/json': { schema: z.object({ a: z.string() }) }, 12 }, 13 }, 14 responses: { 15 200: { 16 description: '200 OK', 17 content: { 18 'application/json': { 19 schema: z.object({ a: z.string() }), 20 }, 21 }, 22 }, 23 }, 24 }, 25 }, 26 }, 27 }, 28 }, 29 }, 30 }, 31});
OpenAPI allows you to define reusable components and this library allows you to replicate that in two separate ways.
If we take the example in createDocument
and instead create title
as follows
1const title = z.string().meta({ 2 description: 'Job title', 3 example: 'My job', 4 id: 'jobTitle', // <- new field 5});
Wherever title
is used in schemas across the document, it will instead be created as a reference.
1{ "$ref": "#/components/schemas/jobTitle" }
title
will then be outputted as a schema within the components section of the documentation.
1{ 2 "components": { 3 "schemas": { 4 "jobTitle": { 5 "type": "string", 6 "description": "Job title", 7 "example": "My job" 8 } 9 } 10 } 11}
This is a great way to create less repetitive Open API documentation. There are some Open API features like discriminator mapping which require all schemas in the union to contain a ref.
Another way to register schema instead of adding a ref
is to add it to the components directly. This will still work in the same way as ref
. So whenever we run into that Zod type we will replace it with a reference.
eg.
1const title = z.string().meta({ 2 description: 'Job title', 3 example: 'My job', 4}); 5createDocument({ 6 components: { 7 schemas: { 8 jobTitle: title, // this will register this Zod Schema as jobTitle unless `id` in `.meta()` is specified on the type 9 }, 10 }, 11});
Query, Path, Header & Cookie parameters can be similarly registered:
1// Easy auto registration 2const jobId = z.string().meta({ 3 description: 'Job ID', 4 example: '1234', 5 param: { id: 'jobRef' }, 6}); 7 8createDocument({ 9 paths: { 10 '/jobs/{jobId}': { 11 put: { 12 requestParams: { 13 header: z.object({ 14 jobId, 15 }), 16 }, 17 }, 18 }, 19 }, 20}); 21 22// or more verbose auto registration 23const jobId = z.string().meta({ 24 description: 'Job ID', 25 example: '1234', 26 param: { in: 'header', name: 'jobId', id: 'jobRef' }, 27}); 28 29createDocument({ 30 paths: { 31 '/jobs/{jobId}': { 32 put: { 33 parameters: [jobId], 34 }, 35 }, 36 }, 37}); 38 39// or manual registration 40const otherJobId = z.string().meta({ 41 description: 'Job ID', 42 example: '1234', 43 param: { in: 'header', name: 'jobId' }, 44}); 45 46createDocument({ 47 components: { 48 parameters: { 49 jobRef: jobId, 50 }, 51 }, 52});
Response headers can be similarly registered:
1const header = z.string().meta({ 2 description: 'Job ID', 3 example: '1234', 4 header: { id: 'some-header' }, 5}); 6 7// or 8 9const jobIdHeader = z.string().meta({ 10 description: 'Job ID', 11 example: '1234', 12}); 13 14createDocument({ 15 components: { 16 headers: { 17 someHeaderRef: jobIdHeader, 18 }, 19 }, 20});
Entire Responses can also be registered
1const response: ZodOpenApiResponseObject = { 2 description: '200 OK', 3 content: { 4 'application/json': { 5 schema: z.object({ a: z.string() }), 6 }, 7 }, 8 id: 'some-response', 9}; 10 11//or 12 13const response: ZodOpenApiResponseObject = { 14 description: '200 OK', 15 content: { 16 'application/json': { 17 schema: z.object({ a: z.string() }), 18 }, 19 }, 20}; 21 22createDocument({ 23 components: { 24 responses: { 25 'some-response': response, 26 }, 27 }, 28});
Callbacks can also be registered
1const callback: ZodOpenApiCallbackObject = { 2 id: 'some-callback' 3 post: { 4 responses: { 5 200: { 6 description: '200 OK', 7 content: { 8 'application/json': { 9 schema: z.object({ a: z.string() }), 10 }, 11 }, 12 }, 13 }, 14 }, 15}; 16 17//or 18 19const callback: ZodOpenApiCallbackObject = { 20 post: { 21 responses: { 22 200: { 23 description: '200 OK', 24 content: { 25 'application/json': { 26 schema: z.object({ a: z.string() }), 27 }, 28 }, 29 }, 30 }, 31 }, 32}; 33 34createDocument({ 35 components: { 36 callbacks: { 37 'some-callback': callback, 38 }, 39 }, 40});
Path Items can also be registered
1const pathItem: ZodOpenApiPathItemObject = { 2 id: 'some-path-item', 3 get: { 4 responses: { 5 200: { 6 description: '200 OK', 7 content: { 8 'application/json': { 9 schema: z.object({ a: z.string() }), 10 }, 11 }, 12 }, 13 }, 14 }, 15}; 16 17// or 18 19createDocument({ 20 components: { 21 pathItems: { 22 'some-path-item': pathItem, 23 }, 24 }, 25});
Security Schemes can be registered for authentication methods:
1createDocument({ 2 components: { 3 securitySchemes: { 4 bearerAuth: { 5 type: 'http', 6 scheme: 'bearer', 7 bearerFormat: 'JWT', 8 description: 'JWT Authentication', 9 }, 10 }, 11 }, 12});
Links can be registered to describe relationships between operations:
1const link: ZodOpenApiLinkObject = { 2 id: 'getUserById', 3 operationId: 'getUser', 4 parameters: { 5 userId: '$request.path.id', 6 }, 7 description: 'Link to get user by id', 8}; 9 10// or 11 12createDocument({ 13 components: { 14 links: { 15 getUserById: { 16 operationId: 'getUser', 17 parameters: { 18 userId: '$request.path.id', 19 }, 20 description: 'Link to get user by id', 21 }, 22 }, 23 }, 24});
Examples can be registered to provide sample values for schemas:
1const example: ZodOpenApiExampleObject = { 2 id: 'userExample', 3 summary: 'A sample user', 4 value: { 5 id: '123', 6 name: 'Jane Doe', 7 email: 'jane@example.com', 8 }, 9}; 10 11// or 12 13createDocument({ 14 components: { 15 examples: { 16 userExample: { 17 summary: 'A sample user', 18 value: { 19 id: '123', 20 name: 'Jane Doe', 21 email: 'jane@example.com', 22 }, 23 }, 24 }, 25 }, 26});
Zod types are composed of two different parts: the input and the output. This library decides which type to create based on if it is used in a request or response context.
Input:
Output:
In general, you want to avoid using a registered input schema in an output context and vice versa. This is because the rendered input and output schemas of a simple Zod schema will differ, even with a simple Zod schema like z.object()
.
1const schema = z.object({ 2 name: z.string(), 3});
Input schemas (request bodies, parameters):
1{ 2 "type": "object", 3 "properties": { 4 "name": { 5 "type": "string" 6 } 7 }, 8 "required": ["name"] 9}
Output schemas (responses):
1{ 2 "type": "object", 3 "properties": { 4 "name": { 5 "type": "string" 6 } 7 }, 8 "required": ["name"], 9 "additionalProperties": false 10}
When the same schema is referenced in both input and output contexts, the library generates two separate component schemas. This happens automatically when a schema with an ID is used in both contexts.
You can customize the output schema name by providing an outputId
:
1const schema = z 2 .object({ 3 name: z.string(), 4 }) 5 .meta({ 6 id: 'MyObject', 7 outputId: 'MyObjectResponse', // Customize the output schema name 8 });
You can also set a global suffix for output schemas or use z.looseObject()
and z.strictObject()
to have explicit control over the schema behavior.
⚠️ Note: If your registered schema contains dynamically created lazy components, they won't be reused between input and output schemas.
Currently the following versions of OpenAPI are supported
3.1.0
(minimum version)3.1.1
Setting the openapi
field will change how the some of the components are rendered.
1createDocument({ 2 openapi: '3.1.0', 3});
As an example z.string().nullable()
will be rendered differently
3.0.0
1{ 2 "type": "string", 3 "nullable": true 4}
3.1.0
1{ 2 "type": ["string", "null"] 3}
See the library in use in the examples folder.
fastify-zod-openapi - Fastify plugin for zod-openapi. This includes type provider, Zod schema validation, Zod schema serialization and Swagger UI support.
eslint-plugin-zod-openapi - Eslint rules for zod-openapi. This includes features which can autogenerate Typescript comments for your Zod types based on your description
, example
and deprecated
fields.
For information about changes and migration from v4 to v5, see the v5 migration guide.
1pnpm 2pnpm build
1pnpm test
1# Fix issues 2pnpm format 3 4# Check for issues 5pnpm lint
To release a new version
🏷️ Choose a tag
, enter a version number. eg. v1.2.0
and click + Create new tag: vX.X.X on publish
.Generate release notes
button and adjust the description.Set as the latest release
box and click Publish release
. This will trigger the Release
workflow.Pull Requests
tab for a PR labelled Release vX.X.X
.Merge Pull Request
on that Pull Request to update master with the new package version.To release a new beta version
🏷️ Choose a tag
, enter a version number with a -beta.X
suffix eg. v1.2.0-beta.1
and click + Create new tag: vX.X.X-beta.X on publish
.Generate release notes
button and adjust the description.Set as a pre-release
box and click Publish release
. This will trigger the Prerelease
workflow.No vulnerabilities found.
No security vulnerabilities found.