zod-openapi 5.0.0-beta.2 → 5.0.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,18 +3,21 @@
3
3
  <h1 align="center">zod-openapi</h1>
4
4
  </p>
5
5
  <p align="center">
6
- A TypeScript library which uses <a href="https://github.com/colinhacks/zod">Zod</a> schemas to generate OpenAPI v3.x documentation.
6
+ A TypeScript library which uses <a href="https://github.com/colinhacks/zod">Zod</a> schemas to generate OpenAPI v3.1x documentation.
7
7
  </p>
8
8
  <div align="center">
9
9
  <a href="https://www.npmjs.com/package/zod-openapi"><img src="https://img.shields.io/npm/v/zod-openapi"/></a>
10
10
  <a href="https://www.npmjs.com/package/zod-openapi"><img src="https://img.shields.io/npm/dm/zod-openapi"/></a>
11
- <a href="https://nodejs.org/en/"><img src="https://img.shields.io/badge/node-%3E%3D%2018-brightgreen"/></a>
11
+ <a href="https://nodejs.org/en/"><img src="https://img.shields.io/node/v/zod-openapi"/></a>
12
12
  <a href="https://github.com/samchungy/zod-openapi/actions/workflows/test.yml"><img src="https://github.com/samchungy/zod-openapi/actions/workflows/test.yml/badge.svg"/></a>
13
13
  <a href="https://github.com/samchungy/zod-openapi/actions/workflows/release.yml"><img src="https://github.com/samchungy/zod-openapi/actions/workflows/release.yml/badge.svg"/></a>
14
14
  <a href="https://github.com/seek-oss/skuba"><img src="https://img.shields.io/badge/🤿%20skuba-powered-009DC4"/></a>
15
15
  </div>
16
16
  <br>
17
17
 
18
+ > [!TIP]
19
+ > Zod v4 support is available via `zod-openapi@beta`. Please read the [documentation](https://github.com/samchungy/zod-openapi/blob/v4-stash/docs/v5.md) for more information. A codegen will be available on release to help you migrate if you wish to wait.
20
+
18
21
  ## Installation
19
22
 
20
23
  Install via `npm`, `yarn`, or `pnpm`:
@@ -31,33 +34,41 @@ pnpm install zod zod-openapi
31
34
 
32
35
  ### `.meta()`
33
36
 
34
- Use the `.meta()` method to add metadata to a Zod schema. It accepts an object with the following options:
37
+ Use the `.meta()` method to add OpenAPI metadata to a Zod schema. It accepts an object with the following options:
35
38
 
36
39
  | Option | Description |
37
40
  | ---------- | ---------------------------------------------------------------------------------------------------------------- |
38
41
  | `id` | Registers a schema as a reusable OpenAPI component. |
39
42
  | `header` | Adds metadata for [response headers](#response-headers). |
40
43
  | `param` | Adds metadata for [request parameters](#parameters). |
41
- | `override` | Allows you to override the rendered OpenAPI schema. This takes either an object or a function |
44
+ | `override` | Allows you to override the rendered OpenAPI schema. This takes either an object or a function. |
42
45
  | `outputId` | Allows you to set a different ID for the output schema. This is useful when the input and output schemas differ. |
43
- | `unusedIO` | Allows you to set the `io` for an unused schema added to the components section. Defaults to `output` |
46
+ | `unusedIO` | Allows you to set the `io` for an unused schema added to the components section. Defaults to `output`. |
47
+
48
+ You can also set standard OpenAPI properties directly in the `.meta()` method, such as:
49
+
50
+ ```typescript
51
+ z.string().meta({
52
+ description: 'A text field',
53
+ example: 'Example value',
54
+ });
55
+ ```
44
56
 
45
57
  ### `createDocument`
46
58
 
47
59
  Generates an OpenAPI documentation object.
48
60
 
49
61
  ```typescript
50
- import 'zod-openapi/extend';
51
- import { z } from 'zod';
62
+ import * as z from 'zod/v4';
52
63
  import { createDocument } from 'zod-openapi';
53
64
 
54
- const jobId = z.string().openapi({
65
+ const jobId = z.string().meta({
55
66
  description: 'A unique identifier for a job',
56
67
  example: '12345',
57
68
  id: 'jobId',
58
69
  });
59
70
 
60
- const title = z.string().openapi({
71
+ const title = z.string().meta({
61
72
  description: 'Job title',
62
73
  example: 'My job',
63
74
  });
@@ -170,37 +181,45 @@ const document = createDocument({
170
181
  ```
171
182
  </details>
172
183
 
173
- #### CreateDocumentOptions
174
-
175
- `createDocument` takes an optional `CreateDocumentOptions` argument which can be used to modify how the document is created.
184
+ `createDocument` takes an optional options argument which can be used to modify how the document is created
176
185
 
177
186
  ```typescript
178
- const document = createDocument(details, {
179
- override: ({ jsonSchema, zodSchema }) => {
180
- if (jsonSchema.anyOf) {
181
- ctx.jsonSchema.oneOf = ctx.jsonSchema.anyOf;
182
- delete ctx.jsonSchema.anyOf;
187
+ createDocument(doc, {
188
+ override: ({ jsonSchema, zodSchema, io }) => {
189
+ // Customize the schema generation
190
+ if (io === 'output') {
191
+ jsonSchema.type = 'string';
183
192
  }
184
193
  },
185
194
  });
186
195
  ```
187
196
 
197
+ #### CreateDocumentOptions
198
+
199
+ | Option | Type | Default | Description |
200
+ | ------------------ | ------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
201
+ | `override` | `Function` | `undefined` | Override rendered schema with a function`` |
202
+ | `outputIdSuffix` | `string` | `'Output'` | Suffix for output schema IDs when the schema is used in both a request and response |
203
+ | `allowEmptySchema` | `boolean \| Object` | `false` | Control whether empty schemas are allowed. |
204
+ | `cycles` | `'ref' \| 'throw'` | `'ref'` | How to handle cycles in schemas.<br>- `'ref'` — Break cycles using $defs<br>- `'throw'` — Error on cycles |
205
+ | `reused` | `'ref' \| 'inline'` | `'inline'` | How to handle reused schemas.<br>- `'ref'` — Reused schemas as references<br>- `'inline'` — Inline reused schemas |
206
+ | `schemaRefPath` | `string` | `'#/components/schemas/'` | Path prefix for schema references. Used when generating $ref values. |
207
+
188
208
  ### `createSchema`
189
209
 
190
210
  Creates an OpenAPI Schema Object along with any registered components. OpenAPI 3.1.0 Schema Objects are fully compatible with JSON Schema.
191
211
 
192
212
  ```typescript
193
- import 'zod-openapi/extend';
194
- import { z } from 'zod';
213
+ import * as z from 'zod/v4';
195
214
  import { createSchema } from 'zod-openapi';
196
215
 
197
- const jobId = z.string().openapi({
216
+ const jobId = z.string().meta({
198
217
  description: 'A unique identifier for a job',
199
218
  example: '12345',
200
219
  id: 'jobId',
201
220
  });
202
221
 
203
- const title = z.string().openapi({
222
+ const title = z.string().meta({
204
223
  description: 'Job title',
205
224
  example: 'My job',
206
225
  });
@@ -245,15 +264,17 @@ const { schema, components } = createSchema(job);
245
264
 
246
265
  #### CreateSchemaOptions
247
266
 
248
- `createSchema` takes an optional `CreateSchemaOptions` parameter which can also take the same options as [CreateDocumentOptions](#createdocumentoptions) along with the following options:
267
+ `createSchema` takes an optional `CreateSchemaOptions` parameter which includes all options from [CreateDocumentOptions](#createdocumentoptions) plus the following:
249
268
 
250
269
  ```typescript
251
270
  const { schema, components } = createSchema(job, {
252
- schemaType: 'input'; // This controls whether this should be rendered as a request (`input`) or response (`output`). Defaults to `output`
253
- openapi: '3.0.0'; // OpenAPI version to use, defaults to `'3.1.0'`
254
- components: { jobId: z.string() } // Additional components to use and create while rendering the schema
255
- componentRefPath: '#/definitions/' // Defaults to #/components/schemas/
256
- })
271
+ // Input/Output context - controls how schemas are generated
272
+ io: 'input', // 'input' for request bodies/params, 'output' for responses
273
+
274
+ // Component handling
275
+ schemaComponents: { jobId: z.string() }, // Pre-defined components to use
276
+ schemaComponentRefPath: '#/definitions/', // Custom path prefix for component references
277
+ });
257
278
  ```
258
279
 
259
280
  ### Request Parameters
@@ -285,7 +306,7 @@ createDocument({
285
306
  '/jobs/{a}': {
286
307
  put: {
287
308
  parameters: [
288
- z.string().openapi({
309
+ z.string().meta({
289
310
  param: {
290
311
  name: 'job-header',
291
312
  in: 'header',
@@ -396,7 +417,7 @@ If we take the example in `createDocument` and instead create `title` as follows
396
417
  ##### Auto Registering Schema
397
418
 
398
419
  ```typescript
399
- const title = z.string().openapi({
420
+ const title = z.string().meta({
400
421
  description: 'Job title',
401
422
  example: 'My job',
402
423
  id: 'jobTitle', // <- new field
@@ -434,14 +455,14 @@ Another way to register schema instead of adding a `ref` is to add it to the com
434
455
  eg.
435
456
 
436
457
  ```typescript
437
- const title = z.string().openapi({
458
+ const title = z.string().meta({
438
459
  description: 'Job title',
439
460
  example: 'My job',
440
461
  });
441
462
  createDocument({
442
463
  components: {
443
464
  schemas: {
444
- jobTitle: title, // this will register this Zod Schema as jobTitle unless `ref` in `.openapi()` is specified on the type
465
+ jobTitle: title, // this will register this Zod Schema as jobTitle unless `id` in `.meta()` is specified on the type
445
466
  },
446
467
  },
447
468
  });
@@ -453,7 +474,7 @@ Query, Path, Header & Cookie parameters can be similarly registered:
453
474
 
454
475
  ```typescript
455
476
  // Easy auto registration
456
- const jobId = z.string().openapi({
477
+ const jobId = z.string().meta({
457
478
  description: 'Job ID',
458
479
  example: '1234',
459
480
  param: { id: 'jobRef' },
@@ -474,7 +495,7 @@ createDocument({
474
495
  });
475
496
 
476
497
  // or more verbose auto registration
477
- const jobId = z.string().openapi({
498
+ const jobId = z.string().meta({
478
499
  description: 'Job ID',
479
500
  example: '1234',
480
501
  param: { in: 'header', name: 'jobId', id: 'jobRef' },
@@ -490,8 +511,8 @@ createDocument({
490
511
  },
491
512
  });
492
513
 
493
- // or manual registeration
494
- const otherJobId = z.string().openapi({
514
+ // or manual registration
515
+ const otherJobId = z.string().meta({
495
516
  description: 'Job ID',
496
517
  example: '1234',
497
518
  param: { in: 'header', name: 'jobId' },
@@ -511,7 +532,7 @@ createDocument({
511
532
  Response headers can be similarly registered:
512
533
 
513
534
  ```typescript
514
- const header = z.string().openapi({
535
+ const header = z.string().meta({
515
536
  description: 'Job ID',
516
537
  example: '1234',
517
538
  header: { id: 'some-header' },
@@ -519,7 +540,7 @@ const header = z.string().openapi({
519
540
 
520
541
  // or
521
542
 
522
- const jobIdHeader = z.string().openapi({
543
+ const jobIdHeader = z.string().meta({
523
544
  description: 'Job ID',
524
545
  example: '1234',
525
546
  });
@@ -615,6 +636,121 @@ createDocument({
615
636
  });
616
637
  ```
617
638
 
639
+ #### Path Items
640
+
641
+ Path Items can also be registered
642
+
643
+ ```typescript
644
+ const pathItem: ZodOpenApiPathItemObject = {
645
+ id: 'some-path-item',
646
+ get: {
647
+ responses: {
648
+ 200: {
649
+ description: '200 OK',
650
+ content: {
651
+ 'application/json': {
652
+ schema: z.object({ a: z.string() }),
653
+ },
654
+ },
655
+ },
656
+ },
657
+ },
658
+ };
659
+
660
+ // or
661
+
662
+ createDocument({
663
+ components: {
664
+ pathItems: {
665
+ 'some-path-item': pathItem,
666
+ },
667
+ },
668
+ });
669
+ ```
670
+
671
+ #### Security Schemes
672
+
673
+ Security Schemes can be registered for authentication methods:
674
+
675
+ ```typescript
676
+ createDocument({
677
+ components: {
678
+ securitySchemes: {
679
+ bearerAuth: {
680
+ type: 'http',
681
+ scheme: 'bearer',
682
+ bearerFormat: 'JWT',
683
+ description: 'JWT Authentication',
684
+ },
685
+ },
686
+ },
687
+ });
688
+ ```
689
+
690
+ #### Links
691
+
692
+ Links can be registered to describe relationships between operations:
693
+
694
+ ```typescript
695
+ const link: ZodOpenApiLinkObject = {
696
+ id: 'getUserById',
697
+ operationId: 'getUser',
698
+ parameters: {
699
+ userId: '$request.path.id',
700
+ },
701
+ description: 'Link to get user by id',
702
+ };
703
+
704
+ // or
705
+
706
+ createDocument({
707
+ components: {
708
+ links: {
709
+ getUserById: {
710
+ operationId: 'getUser',
711
+ parameters: {
712
+ userId: '$request.path.id',
713
+ },
714
+ description: 'Link to get user by id',
715
+ },
716
+ },
717
+ },
718
+ });
719
+ ```
720
+
721
+ #### Examples
722
+
723
+ Examples can be registered to provide sample values for schemas:
724
+
725
+ ```typescript
726
+ const example: ZodOpenApiExampleObject = {
727
+ id: 'userExample',
728
+ summary: 'A sample user',
729
+ value: {
730
+ id: '123',
731
+ name: 'Jane Doe',
732
+ email: 'jane@example.com',
733
+ },
734
+ };
735
+
736
+ // or
737
+
738
+ createDocument({
739
+ components: {
740
+ examples: {
741
+ userExample: {
742
+ summary: 'A sample user',
743
+ value: {
744
+ id: '123',
745
+ name: 'Jane Doe',
746
+ email: 'jane@example.com',
747
+ },
748
+ },
749
+ },
750
+ },
751
+ });
752
+ ```
753
+
618
754
  ### Zod Types
619
755
 
620
756
  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.
@@ -631,13 +767,13 @@ Output:
631
767
 
632
768
  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()`.
633
769
 
634
- ```ts
770
+ ```typescript
635
771
  const schema = z.object({
636
772
  name: z.string(),
637
773
  });
638
774
  ```
639
775
 
640
- Input:
776
+ Input schemas (request bodies, parameters):
641
777
 
642
778
  ```json
643
779
  {
@@ -651,7 +787,7 @@ Input:
651
787
  }
652
788
  ```
653
789
 
654
- Output:
790
+ Output schemas (responses):
655
791
 
656
792
  ```json
657
793
  {
@@ -666,25 +802,30 @@ Output:
666
802
  }
667
803
  ```
668
804
 
669
- Unless you are strictly using `z.looseObject()`s or `z.strictObject()`s throughout your codebase you will likely run into issues where the input and output schemas differ. This library will do a best effort to check equality using a simple JSON.stringify() === JSON.stringify() check. If your registered schema contains dynamically created components, this will always fail.
805
+ 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.
670
806
 
671
- If the schemas are not equal, it will automatically handle this by outputting the `output` schema with an `Output` suffix.
672
-
673
- You can override this by setting the `outputId` field with the `.meta()` method.
807
+ You can customize the output schema name by providing an `outputId`:
674
808
 
675
809
  ```ts
676
810
  const schema = z
677
811
  .object({
678
812
  name: z.string(),
679
813
  })
680
- .meta({ id: 'MyObject', outputId: 'MyObjectResponse' });
814
+ .meta({
815
+ id: 'MyObject',
816
+ outputId: 'MyObjectResponse', // Customize the output schema name
817
+ });
681
818
  ```
682
819
 
820
+ 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.
821
+
822
+ > **⚠️ Note:** If your registered schema contains dynamically created lazy components, they won't be reused between input and output schemas.
823
+
683
824
  ## Supported OpenAPI Versions
684
825
 
685
826
  Currently the following versions of OpenAPI are supported
686
827
 
687
- - `3.1.0`
828
+ - `3.1.0` (minimum version)
688
829
  - `3.1.1`
689
830
 
690
831
  Setting the `openapi` field will change how the some of the components are rendered.
@@ -726,6 +867,10 @@ See the library in use in the [examples](./examples/) folder.
726
867
 
727
868
  - [eslint-plugin-zod-openapi](https://github.com/samchungy/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.
728
869
 
870
+ ## Version Information
871
+
872
+ For information about changes and migration from v4 to v5, see the [v5 migration guide](./docs/v5.md).
873
+
729
874
  ## Comparisons
730
875
 
731
876
  ### [@asteasolutions/zod-to-openapi](./docs/comparisons.md)
package/api/index.d.ts CHANGED
@@ -1 +1 @@
1
- export * from "../dist/api";
1
+ export * from '../dist/api';
package/dist/api.d.mts CHANGED
@@ -1,5 +1,11 @@
1
- export { createComponents, createRegistry } from './create/components.js';
2
- export { createMediaTypeObject } from './create/content.js';
3
- export { createParameter } from './create/parameters.js';
4
- export { unwrapZodObject } from './create/object.js';
5
- export { Override, isAnyZodType } from './zod.js';
1
+ import { ComponentRegistry, Override, createComponents, createRegistry } from "./components-DkESnIB9.mjs";
2
+ import { $ZodObject, $ZodType, $ZodTypes } from "zod/v4/core";
3
+ import { core } from "zod/v4";
4
+
5
+ //#region src/create/object.d.ts
6
+ declare const unwrapZodObject: (zodType: $ZodTypes, io: "input" | "output", path: string[]) => $ZodObject;
7
+ //#endregion
8
+ //#region src/zod.d.ts
9
+ declare const isAnyZodType: (schema: unknown) => schema is core.$ZodTypes;
10
+ //#endregion
11
+ export { ComponentRegistry, Override, createComponents, createRegistry, isAnyZodType, unwrapZodObject };
package/dist/api.d.ts CHANGED
@@ -1,5 +1,11 @@
1
- export { createComponents, createRegistry } from './create/components.js';
2
- export { createMediaTypeObject } from './create/content.js';
3
- export { createParameter } from './create/parameters.js';
4
- export { unwrapZodObject } from './create/object.js';
5
- export { Override, isAnyZodType } from './zod.js';
1
+ import { ComponentRegistry, Override, createComponents, createRegistry } from "./components-BLmIpmmY.js";
2
+ import { $ZodObject, $ZodType, $ZodTypes } from "zod/v4/core";
3
+ import { core } from "zod/v4";
4
+
5
+ //#region src/create/object.d.ts
6
+ declare const unwrapZodObject: (zodType: $ZodTypes, io: "input" | "output", path: string[]) => $ZodObject;
7
+ //#endregion
8
+ //#region src/zod.d.ts
9
+ declare const isAnyZodType: (schema: unknown) => schema is core.$ZodTypes;
10
+ //#endregion
11
+ export { ComponentRegistry, Override, createComponents, createRegistry, isAnyZodType, unwrapZodObject };
package/dist/api.js ADDED
@@ -0,0 +1,6 @@
1
+ const require_components = require('./components-XnOyQ3JZ.js');
2
+
3
+ exports.createComponents = require_components.createComponents;
4
+ exports.createRegistry = require_components.createRegistry;
5
+ exports.isAnyZodType = require_components.isAnyZodType;
6
+ exports.unwrapZodObject = require_components.unwrapZodObject;
package/dist/api.mjs CHANGED
@@ -1,8 +1,3 @@
1
- import { createComponents, createMediaTypeObject, createParameter, createRegistry, unwrapZodObject } from "./components.chunk.mjs";
2
- export {
3
- createComponents,
4
- createMediaTypeObject,
5
- createParameter,
6
- createRegistry,
7
- unwrapZodObject
8
- };
1
+ import { createComponents, createRegistry, isAnyZodType, unwrapZodObject } from "./components-BVy-T4wz.mjs";
2
+
3
+ export { createComponents, createRegistry, isAnyZodType, unwrapZodObject };