mrkeyoor.com_
Sat 08 Aug 22:02 UTC
npmWeb Backendupdated 08 Aug 2026

@asteasolutions/zod-to-openapi

@asteasolutions/zod-to-openapi turns Zod 4 schemas and a registry of route descriptions into OpenAPI 3.0, 3.1, or 3.2 JavaScript objects. It can add OpenAPI metadata through a Zod prototype extension or Zod's native meta method, reuse registered component references, describe parameters, bodies, responses, webhooks, and security schemes, and generate either components alone or a complete document. It removes duplicate schema writing, but it does not discover routes, validate requests automatically, serve Swagger UI, serialize YAML, or guarantee that runtime handlers match the document.

Verdict

This is a strong fit for Zod 4 backends that accept explicit route registration and a build-time generation step. Skip it if you want framework discovery or still run Zod 3, and keep runtime validation plus OpenAPI linting as separate responsibilities.

API stability4/5The registry and generator model has a clear, typed surface, and version 9 exposes separate generators for OpenAPI 3.0, 3.1, and 3.2 rather than silently changing dialects. Stability is reduced by its tight Zod-major coupling: current releases require Zod 4, while Zod 3 users must remain on an older, no-longer-actively-supported line.
Docs5/5The README is a full reference with output examples for schemas, routes, parameters, webhooks, custom components, build integration, generator options, every supported Zod type, known limitations, and differences among OpenAPI versions. It also explains when Zod meta is enough and when the prototype extension remains necessary.
Maintenance5/5Version 9.1.0 was published and the repository was pushed on July 19, 2026. The current release supports Zod 4 and OpenAPI 3.2 alongside 3.0 and 3.1, and the project tracks changes through GitHub releases. Active specification and peer-major work is visible even though 32 open issues and PRs show a meaningful edge-case queue.
Ecosystem5/5The package records 3,586,170 weekly downloads and 1,607 GitHub stars, sits directly on the widely used Zod 4 API, and produces standard OpenAPI objects typed through openapi3-ts. Output works with Swagger UI, client generators, validators, and documentation tools because the package stops at the standard document boundary.

Use it if

  • Your API already validates inputs or outputs with Zod 4 and duplicated OpenAPI schemas keep drifting
  • You want typed registration of paths, parameters, bodies, responses, webhooks, and reusable components
  • You need to target OpenAPI 3.0, 3.1, or 3.2 from the same Zod source
  • You can run a documentation generation script during build or CI and review the resulting artifact
Skip it if

Setup reality

Install @asteasolutions/zod-to-openapi together with its required peer zod@^4. Version 9.1.0 supports CommonJS and ESM and includes declarations, but the simplest .openapi examples depend on a one-time global call to extendZodWithOpenApi(z). Put that in a common entry module before any schema module executes. If Webpack tree shaking is enabled, mark the extension module as a side effect or preload it as the README demonstrates; otherwise production can fail while development works. Zod 4's .meta({ id, description, example }) avoids the extension for straightforward schemas, but registered-schema inheritance and separate parameter metadata still require .openapi. A shared OpenAPIRegistry only contains definitions from modules that have actually run, so a build script must import the route modules before calling the generator. Pick OpenApiGeneratorV3, V31, or V32 deliberately because nullability, tuple representation, and newer document fields differ by specification version. generateDocument returns a JavaScript object, not YAML, so install a serializer such as yaml if that is the artifact you need. The package does not connect generated request schemas to Express, Fastify, or another runtime; call the same Zod schema in middleware or handlers. It also does not lint the finished specification or check that documented responses match live behavior, so run an OpenAPI validator and snapshot or diff the generated file in CI. Sort components alphabetically when stable diffs matter, and register raw OpenAPI components for security schemes or structures that are not Zod-shaped.

Patterns

Enable the openapi method onceextend-zod-once

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

extendZodWithOpenApi(z);

Run this common entry module before importing schemas that call .openapi, and preserve it as a side effect in tree-shaken builds.

Attach simple metadata without extending Zoduse-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 covers ordinary schema IDs and metadata; parameter-specific metadata and registered-schema extension cases still need .openapi.

Register a reusable component schemaregister-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),
}));

The returned value is still the Zod schema, so use the same User object for parsing and route documentation.

Document a GET route with a path parameterregister-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' },
  },
});

Registration documents the route only; your web framework must still parse params with the same Zod schema at runtime.

Document a JSON request bodyregister-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 } },
    },
  },
});

A request body wraps media types under content; required belongs to the body object, not the Zod schema.

Reuse a registered path parameterregister-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-level fields go inside param; fields outside param describe the parameter's schema.

Register and apply bearer authenticationadd-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 components are raw OpenAPI objects because authentication schemes are not Zod data schemas.

Generate a complete OpenAPI 3.0 documentgenerate-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' }],
});

Use the V3 generator for OpenAPI 3.0.x; alphabetical components make generated-file diffs easier to review.

Generate OpenAPI 3.1 nullabilitygenerate-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' },
  });

OpenAPI 3.1 uses JSON Schema-style null types, unlike the nullable keyword generated for 3.0.

Describe OpenAPI 3.2 stream itemsdocument-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 is an OpenAPI 3.2 media-type field; do not emit it from the 3.0 or 3.1 generator.

Generate reusable components without routesgenerate-components-only

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

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

generateComponents returns a JavaScript object containing components only, useful when merging into an existing document.

Write the generated document during a buildwrite-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');

Import every registration module before generation. The library returns an object; add a separate serializer if you need YAML.

Alternatives

PackageRegistryPick it when
zod-openapinpmChoose it for another Zod-first OpenAPI generator with a different document-construction style and Zod 4 support
@anatine/zod-openapinpmChoose it when its schema conversion helpers match an existing Zod and NestJS-oriented stack
zod-to-json-schemanpmChoose it when you only need JSON Schema conversion and will assemble OpenAPI paths and operations elsewhere
tsoanpmChoose it when TypeScript controllers and decorators should generate both routes and OpenAPI rather than starting from Zod