ZodからOpenAPIを生成する方法|zod-to-openapiとZod 4ネイティブ変換・ライブラリの選び分け
TypeScriptで書いたZodスキーマを、そのままOpenAPI(Swagger)仕様書に変換したい——それが「zod-to-openapi」で検索される課題です。Zodスキーマを単一の情報源にすれば、バリデーション・型・API仕様書を三重にメンテする必要がなくなります。ただし手段は一つではありません。Zod 4はライブラリなしでOpenAPI形式のJSON Schemaを出力できるようになり、既存の@asteasolutions/zod-to-openapi・zod-openapi・Honoやfastify向けの統合パッケージと、選択肢が並びます。本記事は各手段の役割と選び分け、そして「OpenAPIからZodを起こす」逆方向まで、2026年時点の版で整理します。
まとめ:ZodとOpenAPIを橋渡しする最短ルート
- Zod 4以降なら、まず標準の
z.toJSONSchema(schema,{ target:"openapi-3.0" })を試す。追加ライブラリなしでOpenAPI 3.0互換のスキーマが出る。 - パス・レスポンスを含む完全なOpenAPIドキュメントを組むなら専用ライブラリ。汎用は
@asteasolutions/zod-to-openapi(v9系)かzod-openapi(v6系)。 - フレームワーク統合が要るなら統合版を選ぶ:Honoは
@hono/zod-openapi、Fastifyはfastify-zod-openapi。ルート定義とバリデーションを兼ねられる。 - 方向を取り違えない。「Zod→OpenAPI」は仕様書生成、「OpenAPI→Zod」は既存仕様からのスキーマ/クライアント生成(
openapi-zod-client)で、目的が逆。
以下、それぞれの仕組みと使い分けの基準を具体的なコードで見ていきます。
zod-to-openapiとは:Zodスキーマを起点にAPI仕様を生成する仕組み
ZodはTypeScript向けのスキーマ定義・バリデーションライブラリで、z.object()などで宣言したスキーマから静的な型(z.infer)を同時に得られます。zod-to-openapiが担うのは、このZodスキーマをOpenAPI仕様(旧Swagger)のスキーマ定義へ変換する部分です。
スキーマを「単一の情報源」にして三重管理をやめる
Zodを使わない構成では、実行時バリデーション・TypeScriptの型・OpenAPI仕様書の三つを別々に書き、変更のたびに三箇所を揃える必要があります。ここがズレると「型は通るのに仕様書と実装が食い違う」不具合の温床になります。Zodスキーマを情報源に一本化すると、バリデーションと型はZodが、API仕様書はzod-to-openapi系が同じスキーマから導出するため、二重・三重の手直しが消えます。これが「Zodスキーマを単一の情報源(single source of truth)にする」という言い回しの実体です。
「Zod→OpenAPI」と「OpenAPI→Zod」を取り違えない
検索クエリには「zod openapi」と並んで「openapi to zod schema」「zod from openapi」も混在します。これは向きが逆の別作業です。Zod→OpenAPIは、コード側のZodスキーマからAPI仕様書を生成する流れ(本記事の主題)。OpenAPI→Zodは、既にあるOpenAPI仕様書(外部APIの定義など)からZodスキーマや型付きクライアントを起こす流れで、担当ライブラリも違います。着手前にどちらが目的かを決めておくと、選ぶパッケージを間違えません。逆方向は本記事末尾で扱います。
Zod 4のネイティブ変換:ライブラリなしでOpenAPIスキーマを出力
Zod 4で標準搭載されたz.toJSONSchema()により、これまでzod-to-json-schemaなど外部ライブラリに頼っていたJSON Schema変換がZod本体で完結します。zod-to-json-schemaは2025年11月以降アクティブなメンテナンスを終える方針が示され、Zod 4標準への移行が推奨されています。
targetとioオプション:OpenAPI 3.0を指定する
変換はz.toJSONSchema(schema, options)で行い、targetで出力するスキーマ規格を選びます。既定はdraft-2020-12で、OpenAPI 3.0向けにはopenapi-3.0を指定します(ほかにdraft-07・draft-04)。
import { z } from 'zod';
const User = z.object({
id: z.string(),
age: z.number().int(),
});
const schema = z.toJSONSchema(User, { target: "openapi-3.0" });
ioオプションは変換対象を入力型か出力型かで切り替えます。既定はoutputで、transformなど入出力で型が変わるスキーマではio:"input"を指定してリクエスト側の形を出力します。
.meta()でdescriptionやexampleを付与する
OpenAPIのスキーマに説明や例を載せるには、Zodの.meta()でメタデータを登録します。登録した項目はそのまま生成後のスキーマへ写されます。
const email = z.string().meta({
title: "Email address",
description: "Your email address",
});
この.meta()方式は後述のzod-openapi(samchungy版)も採用しており、Zod標準の作法に沿うため学習コストが小さいのが利点です。
JSON Schemaで表現できない型の扱い
z.bigint()・z.date()・z.map()・z.set()・z.transform()・z.symbol()などはJSON Schemaに対応表現がなく、既定(unrepresentable:"throw")では例外になります。日付を文字列として扱いたいといった場合は、unrepresentable:"any"とoverrideコールバックで変換を明示します。
z.toJSONSchema(z.date(), {
unrepresentable: "any",
override: (ctx) => {
ctx.jsonSchema.type = "string";
ctx.jsonSchema.format = "date-time";
},
});
「Zodでは表現できるがJSON Schema/OpenAPIには落ちない型がある」点は、ネイティブ変換を採用する際に最初に押さえるべき制約です。
OpenAPIドキュメント生成ライブラリの選び方
単一スキーマの変換ではなく、パス・パラメータ・レスポンスを束ねた完全なOpenAPIドキュメントを組むなら、専用ライブラリが向きます。用途別に代表的な4つを整理します。
| ライブラリ | 最新版 | peer zod | 向き先 | メタ付与 |
|---|---|---|---|---|
| @asteasolutions/zod-to-openapi | 9.1.0 | ^4.0.0 | 汎用 | .openapi() |
| zod-openapi (samchungy) | 6.0.0 | ^4.0.0 | 汎用 | .meta() |
| @hono/zod-openapi | 1.5.1 | ^4.0.0 | Hono統合 | .openapi() |
| fastify-zod-openapi | 5.7.0 | ^3.25.74 ||^4.0.0 | Fastify統合 | .meta() |
いずれもZod 4を前提に更新が進んでおり、Zod 3のまま使う場合は後述のとおり旧メジャーへの固定が必要です。
@asteasolutions/zod-to-openapi(汎用・registry方式)
最も広く使われてきた汎用ライブラリで、最新は9.1.0(peer zod ^4.0.0)。Zodを拡張するextendZodWithOpenApi(z)を最初に呼び、.openapi()でメタデータやコンポーネント名を付け、registryにパスを登録してからドキュメントを生成します。
import { extendZodWithOpenApi, OpenAPIRegistry, OpenApiGeneratorV3 } from '@asteasolutions/zod-to-openapi';
import { z } from 'zod';
extendZodWithOpenApi(z);
const registry = new OpenAPIRegistry();
const User = z.object({
id: z.string().openapi({ example: '1212121' }),
name: z.string(),
}).openapi('User');
registry.register('User', User);
const generator = new OpenApiGeneratorV3(registry.definitions);
const doc = generator.generateDocument({ openapi: '3.0.0', info: { title: 'API', version: '1.0.0' } });
OpenAPI 3.1で出力する場合はOpenApiGeneratorV31を使います。Zod 3世代のプロジェクトは7.3.4が最終対応版で、こちらは新規機能追加が止まっています。
zod-openapi(.meta()準拠・monkey patch不要)
samchungy版のzod-openapi(最新6.0.0、peer zod ^4.0.0)は、Zod標準の.meta()でメタデータを扱う点が特徴です。extendZodWithOpenApiのようなZod本体への拡張(monkey patch)が不要で、パッケージを読み込むだけで型補完まで効きます。ドキュメントはcreateDocument()にOpenAPIオブジェクトを渡して組み立てます。
import { createDocument } from 'zod-openapi';
import { z } from 'zod';
const Job = z.object({ id: z.string() }).meta({ id: 'Job' });
const document = createDocument({
openapi: '3.1.0',
info: { title: 'My API', version: '1.0.0' },
paths: { },
});
Zodへの副作用を避けたい、あるいは標準の.meta()に統一したいチームに向きます。
@hono/zod-openapi(Honoのルート定義と統合)
Honoを使うなら、ルーティング・バリデーション・仕様生成を一体化した@hono/zod-openapi(最新1.5.1、peer hono >=4.10.0/zod ^4.0.0)が有力です。内部で@asteasolutions/zod-to-openapi(8.5系)を利用しており、createRouteで定義したルートのスキーマがそのままOpenAPIに反映され、app.doc()で仕様書エンドポイントを公開できます。
import { OpenAPIHono, createRoute, z } from '@hono/zod-openapi';
const app = new OpenAPIHono();
const route = createRoute({
method: 'get',
path: '/users/{id}',
responses: { 200: { description: 'OK' } },
});
app.doc('/doc', { openapi: '3.0.0', info: { title: 'API', version: '1' } });
「バリデーション用のZodと仕様書用の定義を二重に書きたくない」Hono利用者にとっては、これが最短経路です。
fastify-zod-openapi(Fastify 5プラグイン)
Fastify向けはfastify-zod-openapi(最新5.7.0、peer fastify 5/zod ^3.25.74 ||^4.0.0)。前述のzod-openapiをFastifyプラグインとして統合したもので、ルートスキーマにZodを使いつつOpenAPI仕様を生成します。Zod 3系も受け付ける版指定になっており、移行途中のプロジェクトでも導入しやすい構成です。
Zod 4以降、外部ライブラリは必要か:使い分けの基準
Zod 4がz.toJSONSchema()を標準搭載したことで、「ライブラリを入れるべきか」の判断が変わりました。結論から言えば、要件次第で標準機能とライブラリを使い分けるのが妥当で、常にライブラリを入れる時代ではありません。
単発のスキーマをOpenAPI互換のJSON Schemaに変換したいだけなら、z.toJSONSchema(schema,{ target:"openapi-3.0" })で十分で、依存を増やす理由はありません。一方、パス・パラメータ・レスポンス・再利用コンポーネント($ref)を含む完全な仕様書を組むなら、その構築を肩代わりする@asteasolutions/zod-to-openapiやzod-openapiを選ぶ価値があります。ルーティングとバリデーションを同じ定義で兼ねたいなら、HonoやFastifyの統合版が実装量を最も減らします。逆に、フレームワーク統合版をフレームワーク抜きで使うのは適しません。迷ったら「仕様書だけ欲しいのか、動くAPIの一部として欲しいのか」で切り分けると外しません。
逆方向:OpenAPIからZodスキーマと型付きクライアントを生成する
ここまでと逆に、既存のOpenAPI仕様書からZodスキーマを起こしたい場合はopenapi-zod-client(最新1.18.3)を使います。OpenAPI(Swagger)定義を入力に、Zodスキーマと、Zodiosベースの型付きAPIクライアントを生成します。外部の公開APIや別チームが定義した仕様に対して、フロントエンド側で型安全に呼び出したいケースに向きます。「zod-to-openapi」と名前は似ていますが担当は真逆で、自分のコードから仕様書を出すのか(Zod→OpenAPI)、他者の仕様から呼び出しコードを起こすのか(OpenAPI→Zod)で選ぶツールが変わる点に注意してください。
よくある質問
Zodの読み方と概要は?
Zodは「ゾッド」と読み、TypeScript向けのスキーマ宣言・バリデーションライブラリです。基本的なインストールや型推論の使い方はZodの使い方入門で解説しています。本記事はそのZodスキーマをOpenAPIへ変換する応用にあたります。
codegen(コード生成)とzod-to-openapiは何が違う?
zod-to-openapi系は「Zodスキーマ→OpenAPI仕様書」を生成します。一方、OpenAPI仕様書から各言語のサーバー/クライアントコードを生成するのはOpenAPI Generatorなどのコード生成ツールの役割です。Zodで仕様書を出し、その仕様書からコードを起こす、という順で組み合わせることもできます。
どのライブラリを選べばよい?
単発変換はZod 4標準のz.toJSONSchema、汎用の仕様書生成は@asteasolutions/zod-to-openapiかzod-openapi、Hono利用なら@hono/zod-openapi、Fastify利用ならfastify-zod-openapiが基準です。Zod本体への拡張を避けたい場合は.meta()準拠のzod-openapiが扱いやすいです。
Zod 3のプロジェクトでも使える?
使えますが、多くのライブラリの最新版はpeer依存がzod ^4.0.0です。Zod 3のままなら@asteasolutions/zod-to-openapiは7.3.4に固定するなど、Zod 3対応の版へバージョンを合わせる必要があります。fastify-zod-openapiのようにZod 3系を受け付ける版指定のパッケージもあります。
hono/zod-validatorとの違いは?
@hono/zod-validatorはリクエストをZodで検証するためのミドルウェアで、OpenAPI仕様書は生成しません。バリデーションに加えてOpenAPI仕様まで出したい場合は@hono/zod-openapiを選びます。仕様生成が不要なら軽量な@hono/zod-validatorで十分です。