mrkeyoor.com_
Wed 23 Sept 00:37 UTC
npmWeb Backendupdated 22 Sept 2026

@asteasolutions/zod-to-openapi review

@asteasolutions/zod-to-openapi 9.1.0 converts Zod 4 schemas plus explicit route registrations into OpenAPI 3.0, 3.1, or 3.2 documents. It covers component schemas, parameters, request bodies, responses, webhooks, and security schemes. The current line includes an OpenAPI 3.2 generator while retaining separate generators for earlier dialects. Our install included declarations and loaded through both require() and ESM import. It does not inspect framework routes, validate a request for the handler, emit YAML, or prove the running API follows the generated contract.

Verdict

@asteasolutions/zod-to-openapi 9.1.0 took 1.7 seconds and 9 MB in our sandbox, passed npm audit with 0 findings, and produced an 8.1 KB gzipped browser bundle. Install it for a Zod 4 API with explicit route registration; choose a framework generator if route discovery is the requirement.

We installed it

Lab card: what happened when we installed @asteasolutions/zod-to-openapiScreenshot of @asteasolutions/zod-to-openapi documentation
Install✓ · 1.7s10 packages on disk · 9 MB
ImportESM import works · require() works · CommonJS package
Browser8.1 KBgzipped (29.9 KB minified), bundled with esbuild
TypesTypeScript types bundled
Known vulns00 critical · 0 high · 0 moderate · 0 low (npm audit)

Answers from our run

Does @asteasolutions/zod-to-openapi install cleanly?

Yes. In a fresh container with an empty cache, npm install @asteasolutions/zod-to-openapi finished in 2 seconds, leaving 10 packages and 9 MB on disk. npm audit reported no known vulnerabilities.

How much does @asteasolutions/zod-to-openapi add to a browser bundle?

8.1 KB gzipped (29.9 KB minified) when the whole package is bundled for the browser with esbuild. Importing only part of it is usually smaller.

Does @asteasolutions/zod-to-openapi work with both ESM and CommonJS?

Yes. Both import '@asteasolutions/zod-to-openapi' and require('@asteasolutions/zod-to-openapi') worked in Node 22 in our run. The package is published as CommonJS.

Does @asteasolutions/zod-to-openapi include TypeScript types?

Yes, type declarations ship inside the package, so no @types install is needed.

@asteasolutions/zod-to-openapi or zod-openapi: which should you use?

zod-openapi: Use it when its Zod 4 document-building API fits your codebase better. @asteasolutions/zod-to-openapi 9.1.0 took 1.7 seconds and 9 MB in our sandbox, passed npm audit with 0 findings, and produced an 8.1 KB gzipped browser bundle.

When should you not use @asteasolutions/zod-to-openapi?

Your application remains on Zod 3; the README sends that setup to 7.3.4 and says the older line is not actively supported

API stability4/5Version 9.1.0 exposes distinct OpenApiGeneratorV3, V31, and V32 classes, so selecting a newer OpenAPI dialect does not silently rewrite an older document. Registry and generation calls are typed and explicit. The stability cost is the peer-major boundary: 9.x requires Zod 4, while Zod 3 users are directed to the frozen 7.3.4 line.
Docs5/5The README documents schema metadata, route and webhook registration, reusable parameters, raw components, generator selection, YAML serialization, sorting, and the supported Zod type list. It states the awkward details beside their examples, including when .meta() is sufficient, when .openapi() needs the extension, and how additionalProperties differs from ordinary Zod parsing.
Maintenance5/5The repository was pushed on July 19, 2026, and release 9.1.0 supports Zod 4 plus the OpenAPI 3.2 document shape. GitHub reported 34 open issues and PRs when checked, which is a real queue but does not indicate abandonment. The repository is neither archived nor disabled, and its current README covers all 3 generator families.
Ecosystem5/5The npm endpoint recorded 3,915,779 downloads for the week ending August 24, 2026, and GitHub showed 1,611 stars. Output is an ordinary OpenAPI object, so Swagger renderers, linters, client generators, and contract tools need no package adapter. Adoption still assumes Zod 4; the library adds little to an API whose source schemas live elsewhere.

Use it if

  • Zod 4 already validates your API data and maintaining separate OpenAPI schemas causes drift
  • Your build can register paths, bodies, responses, webhooks, and components in one OpenAPIRegistry
  • One schema source must generate OpenAPI 3.0, 3.1, or 3.2 without hand-editing dialect differences
  • You can keep runtime validation and contract testing as explicit jobs outside the generator
Skip it if

Setup reality

Our fresh Node 22 install of @asteasolutions/zod-to-openapi 9.1.0 finished in 1.7 seconds. It left 10 packages and 9 MB on disk, with one direct dependency, one Zod peer, and a 364 KB unpacked package. npm audit reported 0 known vulnerabilities. The package is CommonJS without an exports map, but require() and ESM import both worked. Declarations ship with it. Our full browser import measured 29.9 KB minified and 8.1 KB gzipped.

Install Zod 4 beside it. Simple schema IDs and examples can use Zod .meta(), while registered inheritance and parameter metadata still need extendZodWithOpenApi(z). That call must run before modules using .openapi(). A tree-shaken build also needs the extension entry marked as a side effect. The registry contains definitions only from modules that executed, so the generation script must import every route-registration module before constructing the document.

Choose OpenApiGeneratorV3, V31, or V32 for the consumer because nullability and document fields differ across the 3 versions. generateDocument() returns a JavaScript object, and YAML needs another serializer. Runtime parsing remains the web framework's job even when the handler imports the same Zod schema. CI should lint and diff the generated output because this package cannot detect a handler returning a different status or body.

Patterns

Load the extension before decorated schemas extend-zod-once

import { extendZodWithOpenApi } from '@asteasolutions/zod-to-openapi';
import { z } from 'zod';

extendZodWithOpenApi(z);

One extendZodWithOpenApi(z) call adds .openapi() to the shared Zod instance. Execute this module before any decorated schema module.

Describe a schema through Zod 4 metadata use-zod-meta

import { z } from 'zod';

const UserId = z.string().uuid().meta({
  id: 'UserId',
  description: 'Stable user identifier',
  example: '2f1f9a71-0c0b-4eb5-b4a4-bd27b90fb061',
});

Zod 4 .meta() supplies an ID, description, and example without prototype extension. Parameter metadata and some inheritance cases still require .openapi().

Register a reusable Zod component register-component-schema

import { OpenAPIRegistry } from '@asteasolutions/zod-to-openapi';
import { z } from 'zod';

const registry = new OpenAPIRegistry();
const User = registry.register('User', z.object({
  id: z.string().uuid(),
  name: z.string().min(1),
}));

registry.register() returns the same Zod schema, so User can parse runtime data and appear as a referenced OpenAPI component.

Record a GET operation with a path value register-get-route

registry.registerPath({
  method: 'get',
  path: '/users/{id}',
  summary: 'Get a user',
  request: { params: z.object({ id: z.string().uuid() }) },
  responses: {
    200: {
      description: 'User found',
      content: { 'application/json': { schema: User } },
    },
    404: { description: 'User not found' },
  },
});

registerPath() writes documentation only. The server must still parse id and enforce the 200 and 404 response contracts.

Specify a required JSON request body register-post-body

const CreateUser = z.object({ name: z.string().min(1) });

registry.registerPath({
  method: 'post',
  path: '/users',
  request: {
    body: {
      required: true,
      content: { 'application/json': { schema: CreateUser } },
    },
  },
  responses: {
    201: {
      description: 'User created',
      content: { 'application/json': { schema: User } },
    },
  },
});

The required flag belongs on the OpenAPI request-body object. Zod field rules describe the JSON carried inside it.

Share one registered path parameter register-shared-parameter

const UserId = registry.registerParameter(
  'UserId',
  z.string().uuid().openapi({
    param: { name: 'id', in: 'path' },
    description: 'User identifier',
  }),
);

registry.registerPath({
  method: 'delete',
  path: '/users/{id}',
  request: { params: z.object({ id: UserId }) },
  responses: { 204: { description: 'Deleted' } },
});

Parameter fields such as name and in belong under param. Schema description fields stay outside that nested object.

Apply a bearer security component add-security-scheme

registry.registerComponent('securitySchemes', 'bearerAuth', {
  type: 'http',
  scheme: 'bearer',
  bearerFormat: 'JWT',
});

registry.registerPath({
  method: 'get',
  path: '/me',
  security: [{ bearerAuth: [] }],
  responses: { 200: { description: 'Current user' } },
});

Security schemes are raw OpenAPI components because a Zod value schema cannot express authentication policy.

Produce an OpenAPI 3.0 contract generate-openapi-30

import { OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi';

const document = new OpenApiGeneratorV3(registry.definitions, {
  sortComponents: 'alphabetically',
}).generateDocument({
  openapi: '3.0.3',
  info: { title: 'Users API', version: '1.0.0' },
  servers: [{ url: 'https://api.example.com' }],
});

OpenApiGeneratorV3 emits the 3.0 schema dialect. Alphabetical components keep generated JSON diffs stable.

Use OpenAPI 3.1 null semantics generate-openapi-31

import { OpenApiGeneratorV31 } from '@asteasolutions/zod-to-openapi';

const MaybeName = registry.register('MaybeName', z.string().nullable());
const document = new OpenApiGeneratorV31(registry.definitions)
  .generateDocument({
    openapi: '3.1.0',
    info: { title: 'Example API', version: '1.0.0' },
  });

The 3.1 generator uses JSON Schema null types. OpenAPI 3.0 represents the same nullable Zod schema differently.

Describe stream items in OpenAPI 3.2 document-event-stream

import { OpenApiGeneratorV32 } from '@asteasolutions/zod-to-openapi';

const Event = registry.register('Event', z.object({ message: z.string() }));
registry.registerPath({
  method: 'get',
  path: '/events',
  responses: {
    200: {
      description: 'Event stream',
      content: { 'text/event-stream': { itemSchema: Event } },
    },
  },
});

const document = new OpenApiGeneratorV32(registry.definitions).generateDocument({
  openapi: '3.2.0',
  info: { title: 'Events API', version: '1.0.0' },
});

itemSchema belongs to the OpenAPI 3.2 media-type shape. A 3.0 or 3.1 generator should not receive it.

Extract only the reusable components generate-components-only

import { OpenApiGeneratorV31 } from '@asteasolutions/zod-to-openapi';

const { components } = new OpenApiGeneratorV31(registry.definitions)
  .generateComponents();

generateComponents() returns a JavaScript component object without paths, suitable for merging into an existing 3.1 document.

Write JSON after all route imports write-document-build

import { writeFile } from 'node:fs/promises';
import './routes/users.js';
import { generateOpenAPI } from './openapi.js';

const document = generateOpenAPI();
await writeFile('./dist/openapi.json', JSON.stringify(document, null, 2) + '\n');

Every module calling registry.registerPath() must execute before generation. JSON works directly; YAML requires another serializer.

Alternatives

PackageRegistryPick it when
zod-openapinpmUse it when its Zod 4 document-building API fits your codebase better.
@anatine/zod-openapinpmUse it in an existing Anatine or NestJS-oriented Zod stack.
zod-to-json-schemanpmUse it when JSON Schema is the target and paths will be assembled elsewhere.
tsoanpmUse it when controller types and decorators should generate routes as well as OpenAPI.

More web backend guides

urllib3 · requests · ws · anyio · httpx · undici · the whole shelf →

How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.