Security News
Create React App Officially Deprecated Amid React 19 Compatibility Issues
Create React App is officially deprecated due to React 19 issues and lack of maintenance—developers should switch to Vite or other modern alternatives.
zod-openapi
Advanced tools
A Typescript library to use Zod Schemas to create OpenAPI v3.x documentation
Install via npm
or yarn
:
npm install zod zod-openapi
## or
yarn add zod zod-openapi
extendZodWithOpenApi
Mutates Zod with an .openapi()
method and extra metadata. Make a side-effectful import at the top of your entry point(s).
import { z } from 'zod';
import { extendZodWithOpenApi } from 'zod-openapi';
extendZodWithOpenApi(z);
z.string().openapi({ description: 'hello world!', example: 'hello world' });
createDocument
Creates an OpenAPI documentation object
import { z } from 'zod';
import { createDocument, extendZodWithOpenApi } from 'zod-openapi';
extendZodWithOpenApi(z);
const jobId = z.string().openapi({
description: 'Job ID',
example: '12345',
});
const title = z.string().openapi({
description: 'Job title',
example: 'My job',
});
const document = createDocument({
openapi: '3.1.0',
info: {
title: 'My API',
version: '1.0.0',
},
paths: {
'/jobs/{jobId}': {
put: {
requestParams: { path: z.object({ jobId }) },
requestBody: {
content: {
'application/json': { schema: z.object({ title }) },
},
},
responses: {
'200': {
description: '200 OK',
content: {
'application/json': { schema: z.object({ jobId, title }) },
},
},
},
},
},
},
});
Generates the following object:
{
"openapi": "3.1.0",
"info": {
"title": "My API",
"version": "1.0.0"
},
"paths": {
"/jobs/{jobId}": {
"put": {
"parameters": [
{
"in": "path",
"name": "jobId",
"schema": {
"type": "string",
"description": "Job ID",
"example": "12345"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Job title",
"example": "My job"
}
},
"required": ["title"]
}
}
}
},
"responses": {
"200": {
"description": "200 OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"jobId": {
"type": "string",
"description": "Job ID",
"example": "12345"
},
"title": {
"type": "string",
"description": "Job title",
"example": "My job"
}
},
"required": ["jobId", "title"]
}
}
}
}
}
}
}
}
}
Query, Path, Header & Cookie parameters can be created using the requestParams
key under the method
key as follows:
createDocument({
paths: {
'/jobs/:a': {
put: {
requestParams: {
path: z.object({ a: z.string() }),
query: z.object({ b: z.string() }),
cookie: z.object({ cookie: z.string() }),
header: z.object({ 'custom-header': z.string() }),
},
},
},
},
});
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.
createDocument({
paths: {
'/jobs/:a': {
put: {
parameters: [
z.string().openapi({
param: {
name: 'job-header',
in: 'header',
},
}),
],
},
},
},
});
Where you would normally declare the media type, set the schema
as your Zod Schema as follows.
createDocument({
paths: {
'/jobs': {
get: {
requestBody: {
content: {
'application/json': { schema: z.object({ a: z.string() }) },
},
},
},
},
},
});
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.
createDocument({
paths: {
'/jobs': {
get: {
responses: {
200: {
description: '200 OK',
content: {
'application/json': { schema: z.object({ a: z.string() }) },
},
headers: z.object({
'header-key': z.string(),
}),
},
},
},
},
},
});
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
const title = z.string().openapi({
description: 'Job title',
example: 'My job',
ref: 'jobTitle', // <- new field
});
Wherever title
is used in schemas across the document, it will instead be created as a reference.
{ "$ref": "#/components/schemas/jobTitle" }
title
will then be outputted as a schema within the components section of the documentation.
{
"components": {
"schemas": {
"jobTitle": {
"type": "string",
"description": "Job title",
"example": "My job"
}
}
}
}
This can be an extremely powerful way to generate better 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.
createDocument({
components: {
schemas: {
jobTitle: title, // this will register this Zod Schema as jobTitle unless `ref` in `.openapi()` is specified on the type
},
},
});
.transform()
and .pipe()
are complicated because they technically comprise of two types (input & output). This means that we need to understand which type you are creating. This library will automatically select which type to use by checking how the schema is used based on the following rules:
Input: Request Bodies, Request Parameters, Headers
Output: Responses, Response Headers
If a registered schema with a transform or pipeline is used in both a request and response schema you will receive an error because the created schema for each will be different. To override the creation type for a specific ZodEffect, add an .openapi()
field on it and set the effectType
field to input
or output
. This will force this library to always generate the input/output type even if we are creating a response (output) or request (input) type. You typically want to use this when you know your transform has not changed the type.
.preprocess()
will always return the output
type even if we are creating an input schema. If a different input type is required you can achieve this with a .transform()
combined with a .pipe()
or simply declare a manual type
in .openapi()
.
If you are adding a ZodSchema directly to the components
section which is not referenced anywhere in the document, additional context may be required to create either an input or output schema. You can do this by setting the refType
field to input
or output
in .openapi()
. This defaults to output
by default.
Query, Path, Header & Cookie parameters can be similarly registered:
// Easy auto registration
const jobId = z.string().openapi({
description: 'Job ID',
example: '1234',
param: { ref: 'jobRef' },
});
createDocument({
paths: {
'/jobs/{jobId}': {
put: {
requestParams: {
header: z.object({
jobId,
}),
},
},
},
},
});
// or more verbose auto registration
const jobId = z.string().openapi({
description: 'Job ID',
example: '1234',
param: { in: 'header', name: 'jobId', ref: 'jobRef' },
});
createDocument({
paths: {
'/jobs/{jobId}': {
put: {
parameters: [jobId],
},
},
},
});
// or manual registeration
const otherJobId = z.string().openapi({
description: 'Job ID',
example: '1234',
param: { in: 'header', name: 'jobId' },
});
createDocument({
components: {
parameters: {
jobRef: jobId,
},
},
});
Response headers can be similarly registered:
const header = z.string().openapi({
description: 'Job ID',
example: '1234',
header: { ref: 'some-header' },
});
// or
const jobIdHeader = z.string().openapi({
description: 'Job ID',
example: '1234',
});
createDocument({
components: {
headers: {
someHeaderRef: jobIdHeader,
},
},
});
Entire Responses can also be registered
const response: ZodOpenApiResponseObject = {
description: '200 OK',
content: {
'application/json': {
schema: z.object({ a: z.string() }),
},
},
ref: 'some-response',
};
//or
const response: ZodOpenApiResponseObject = {
description: '200 OK',
content: {
'application/json': {
schema: z.object({ a: z.string() }),
},
},
};
createDocument({
components: {
responses: {
'some-response': response,
},
},
});
Currently the following versions of OpenAPI are supported
3.0.0
3.0.1
3.0.2
3.0.3
3.1.0
Setting the openapi
field will change how the some of the components are rendered.
createDocument({
openapi: '3.1.0',
});
For example in z.string().nullable()
will be rendered differently
3.0.0
{
"type": "string",
"nullable": true
}
3.1.0
{
"type": ["string", "null"]
}
minItems
/maxItems
mapping for .length()
, .min()
, .max()
string
type
mapping by defaultdiscriminator
mapping when all schemas in the union contain a ref
.transform
support for request schemas. See Zod Effects for how to enable response schema supportpre-process
support. We assume that the input type is the same as the output type. Otherwise pipe and transform can be used instead.refine
full supportstring
, number
and combined enums.integer
type
mapping for .int()
exclusiveMin
/min
/exclusiveMax
/max
mapping for .min()
, .max()
, lt()
, gt()
additionalProperties
mapping for .catchall()
, .strict()
allOf
mapping for .extend()
when the base object is registered and does not have catchall()
, strict()
and extension does not override a field.uniqueItems
(you may need to add a pre-process)format
mapping for .url()
, .uuid()
, .email()
, .datetime()
minLength
/maxLength
mapping for .length()
, .min()
, .max()
pattern
mapping for .regex()
items
mapping for .rest()
prefixItems
mapping for OpenAPI 3.1.0+If this library cannot determine a type for a Zod Schema, it will throw an error. To avoid this, declare a manual type
in the .openapi()
section of that schema.
eg.
z.custom().openapi({ type: 'string' });
See the library in use in the examples folder.
description
, example
and deprecated
fields.zod-openapi was created while trying to add a feature to support auto registering schemas. This proved to be extra challenging given the overall structure of the library so I decided re-write the whole thing. I was a big contributor to this library and love everything it's done, however I could not go past a few issues.
The underlying structure of the library consists of tightly coupled classes which require you to create an awkward Registry class to create references. This would mean you would need to ship a registry class instance along with your types which makes sharing types difficult.
No auto registering schema. Most users do not want to think about this so having to import and call .register()
is a nuisance.
When you register a schema using the registry you need to use the outputted type from the .register()
call. You do not need to do such a thing with this library.
No transform support or safety. You can use a type
to override the transform type but what happens when that transform logic changes?
No input/output validation with components. What happens when you register a component with a transform which technically comprises of two types in a request and a response?
Did I really rewrite an entire library just for this? Absolutely. I believe that creating documentation and types should be as simple and as frictionless as possible.
.register()
call made and replace them with ref
in .openapi()
or alternatively, add them directly to the components section of the schema.const registry = new OpenAPIRegistry();
const foo = registry.register(
'foo',
z.string().openapi({ description: 'foo' }),
);
const bar = z.object({ foo });
// Replace with:
const foo = z.string().openapi({ ref: 'foo', description: 'foo' });
const bar = z.object({ foo });
// or
const foo = z.string().openapi({ description: 'foo' });
const bar = z.object({ foo });
const document = createDocument({
components: {
schemas: {
foo,
},
},
});
registry.registerComponent()
with a regular OpenAPI component in the document.const registry = new OpenAPIRegistry();
registry.registerComponent('securitySchemes', 'auth', {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
description: 'An auth token issued by oauth',
});
// Replace with regular component declaration
const document = createDocument({
components: {
// declare directly in components
securitySchemes: {
auth: {
type: 'http',
scheme: 'bearer',
bearerFormat: 'JWT',
description: 'An auth token issued by oauth',
},
},
},
});
registry.registerPath()
with a regular OpenAPI paths in the document.const registry = new OpenAPIRegistry();
registry.registerPath({
method: 'get',
path: '/foo',
request: {
query: z.object({ a: z.string() }),
params: z.object({ b: z.string() }),
body: z.object({ c: z.string() }),
headers: z.object({ d: z.string() })
},
responses: {},
});
// Replace with regular path declaration
const getFoo: ZodOpenApiPathItemObject = {
get: {
requestParams: {
query: z.object({ a: z.string() }),
path: z.object({ b: z.string() }), // params -> path
header: z.object({ c: z.string() }) // headers -> header
}, // renamed from request -> requestParams
requestBody: z.object({c: z.string() }) // request.body -> requestBody
responses: {},
},
};
const document = createDocument({
paths: {
'/foo': getFoo,
},
});
yarn
yarn build
yarn test
# Fix issues
yarn format
# Check for issues
yarn 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.FAQs
Convert Zod Schemas to OpenAPI v3.x documentation
The npm package zod-openapi receives a total of 0 weekly downloads. As such, zod-openapi popularity was classified as not popular.
We found that zod-openapi demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Create React App is officially deprecated due to React 19 issues and lack of maintenance—developers should switch to Vite or other modern alternatives.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.