@kubb/plugin-zod 5.0.0-beta.4 → 5.0.0-beta.42

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/extension.yaml CHANGED
@@ -2,18 +2,18 @@ $schema: https://kubb.dev/schemas/extension.json
2
2
  kind: plugin
3
3
  id: plugin-zod
4
4
  name: Zod
5
- description: Generate Zod validation schemas from OpenAPI specifications for runtime data validation.
5
+ description: Generate Zod v4 schemas from OpenAPI for runtime validation that stays in sync with your TypeScript types.
6
6
  category: validation
7
7
  type: official
8
- npmPackage: "@kubb/plugin-zod"
8
+ npmPackage: '@kubb/plugin-zod'
9
9
  docsPath: /plugins/plugin-zod
10
10
  repo: https://github.com/kubb-labs/plugins
11
11
  maintainers:
12
12
  - name: Stijn Van Hulle
13
13
  github: stijnvanhulle
14
14
  compatibility:
15
- kubb: ">=5.0.0"
16
- node: ">=22"
15
+ kubb: '>=5.0.0'
16
+ node: '>=22'
17
17
  tags:
18
18
  - zod
19
19
  - validation
@@ -34,166 +34,445 @@ icon:
34
34
  intro: |
35
35
  # @kubb/plugin-zod
36
36
 
37
- Generate [Zod](https://zod.dev/) validation schemas from your OpenAPI schema for runtime validation. Kubb generates Zod v4 schemas.
37
+ Generate [Zod](https://zod.dev/) v4 schemas from your OpenAPI spec. Use them to validate API responses at runtime, build form schemas, or feed back into router libraries that consume Zod (`tRPC`, `Hono`, `Elysia`).
38
+
39
+ Pair with `@kubb/plugin-client` and set the client's `parser: 'zod'` to validate every response automatically.
38
40
  options:
39
41
  - name: output
40
42
  type: Output
41
43
  required: false
42
- default: "{ path: 'zod', barrelType: 'named' }"
43
- description: Specify the export location for the files and define the behavior of the output.
44
+ default: "{ path: 'zod', barrel: { type: 'named' } }"
45
+ description: Where the generated Zod schemas are written and how they are exported.
44
46
  properties:
45
47
  - name: path
46
48
  type: string
47
49
  required: true
48
- description: Output directory or file for the generated code, relative to the global `output.path`.
50
+ description: |
51
+ Folder (or single file) where the plugin writes its generated code. The path is resolved against the global `output.path` set on `defineConfig`.
52
+
53
+ Use a folder to keep each generator's output isolated (`'types'`, `'clients'`, `'hooks'`). Use a single file when you want everything in one place, for example `'api.ts'`.
49
54
  tip: |
50
- if `output.path` is a file, `group` cannot be used.
55
+ When `output.path` points to a single file, the `group` option cannot be used because every operation ends up in the same file.
56
+ examples:
57
+ - name: kubb.config.ts
58
+ files:
59
+ - lang: typescript
60
+ twoslash: false
61
+ code: |
62
+ import { defineConfig } from 'kubb'
63
+ import { pluginTs } from '@kubb/plugin-ts'
64
+
65
+ export default defineConfig({
66
+ input: { path: './petStore.yaml' },
67
+ output: { path: './src/gen' },
68
+ plugins: [
69
+ pluginTs({
70
+ output: { path: './types' },
71
+ }),
72
+ ],
73
+ })
74
+ - name: Resulting tree
75
+ files:
76
+ - lang: text
77
+ twoslash: false
78
+ code: |
79
+ src/
80
+ └── gen/
81
+ └── types/
82
+ ├── Pet.ts
83
+ └── Store.ts
51
84
  default: "'zod'"
52
- - name: barrelType
53
- type: "'all' | 'named' | 'propagate' | false"
85
+ - name: barrel
86
+ type: "{ type: 'named' | 'all', nested?: boolean } | false"
54
87
  required: false
55
- default: "'named'"
56
- description: Specify what to export and optionally disable barrel-file generation.
88
+ default: "{ type: 'named' }"
89
+ description: |
90
+ Controls how the generated `index.ts` (barrel) file re-exports the plugin's output.
91
+
92
+ - `{ type: 'named' }` re-exports each symbol by name. Best for tree-shaking and explicit imports.
93
+ - `{ type: 'all' }` uses `export *`. Smaller barrel file, but exports everything.
94
+ - `{ nested: true }` creates a barrel in every subdirectory, so callers can import from any depth.
95
+ - `false` skips the barrel entirely. The plugin's files are also excluded from the root `index.ts`.
57
96
  tip: |
58
- Using `propagate` will prevent a plugin from creating a barrel file, but it will still propagate, allowing [`output.barrelType`](https://kubb.dev/docs/5.x/configuration#output-barreltype) to export the specific function or type.
97
+ Pick `'named'` when consumers care about which symbols they import (better tree-shaking, friendlier auto-import). Pick `'all'` when the file count is small and you want a one-line barrel.
59
98
  examples:
60
- - name: all
99
+ - name: "'named' (default)"
61
100
  files:
62
- - lang: typescript
101
+ - name: kubb.config.ts
102
+ lang: typescript
103
+ twoslash: false
63
104
  code: |
64
- export * from './gen/petService.ts'
105
+ import { defineConfig } from 'kubb'
106
+ import { pluginTs } from '@kubb/plugin-ts'
107
+
108
+ export default defineConfig({
109
+ input: { path: './petStore.yaml' },
110
+ output: { path: './src/gen' },
111
+ plugins: [
112
+ pluginTs({
113
+ output: { barrel: { type: 'named' } },
114
+ }),
115
+ ],
116
+ })
117
+ - name: src/gen/types/index.ts
118
+ lang: typescript
65
119
  twoslash: false
66
- - name: named
120
+ code: |
121
+ export { Pet, PetStatus } from './Pet'
122
+ export { Store } from './Store'
123
+ - name: "'all'"
67
124
  files:
68
- - lang: typescript
125
+ - name: kubb.config.ts
126
+ lang: typescript
127
+ twoslash: false
69
128
  code: |
70
- export { PetService } from './gen/petService.ts'
129
+ import { defineConfig } from 'kubb'
130
+ import { pluginTs } from '@kubb/plugin-ts'
131
+
132
+ export default defineConfig({
133
+ input: { path: './petStore.yaml' },
134
+ output: { path: './src/gen' },
135
+ plugins: [
136
+ pluginTs({
137
+ output: { barrel: { type: 'all' } },
138
+ }),
139
+ ],
140
+ })
141
+ - name: src/gen/types/index.ts
142
+ lang: typescript
71
143
  twoslash: false
72
- - name: propagate
144
+ code: |
145
+ export * from './Pet'
146
+ export * from './Store'
147
+ - name: nested
73
148
  files:
74
- - lang: typescript
75
- code: ""
149
+ - name: kubb.config.ts
150
+ lang: typescript
151
+ twoslash: false
152
+ code: |
153
+ import { defineConfig } from 'kubb'
154
+ import { pluginTs } from '@kubb/plugin-ts'
155
+
156
+ export default defineConfig({
157
+ input: { path: './petStore.yaml' },
158
+ output: { path: './src/gen' },
159
+ plugins: [
160
+ pluginTs({
161
+ output: { barrel: { type: 'named', nested: true } },
162
+ }),
163
+ ],
164
+ })
165
+ - name: Generated tree
166
+ lang: text
76
167
  twoslash: false
77
- - name: "false"
168
+ code: |
169
+ src/gen/types/
170
+ ├── index.ts # re-exports ./petController and ./storeController
171
+ ├── petController/
172
+ │ ├── index.ts # re-exports Pet, Store, ...
173
+ │ └── Pet.ts
174
+ └── storeController/
175
+ ├── index.ts
176
+ └── Store.ts
177
+ - name: 'false'
78
178
  files:
79
- - lang: typescript
80
- code: ""
179
+ - name: kubb.config.ts
180
+ lang: typescript
181
+ twoslash: false
182
+ code: |
183
+ import { defineConfig } from 'kubb'
184
+ import { pluginTs } from '@kubb/plugin-ts'
185
+
186
+ export default defineConfig({
187
+ input: { path: './petStore.yaml' },
188
+ output: { path: './src/gen' },
189
+ plugins: [
190
+ pluginTs({
191
+ output: { barrel: false },
192
+ }),
193
+ ],
194
+ })
195
+ - name: Result
196
+ lang: text
81
197
  twoslash: false
198
+ code: |
199
+ # No index.ts is generated for this plugin.
200
+ # Its files are also excluded from the root index.ts.
82
201
  - name: banner
83
- type: "string | ((node: RootNode) => string)"
202
+ type: 'string | ((node: RootNode) => string)'
84
203
  required: false
85
- description: Add a banner comment at the top of every generated file. Accepts a static string or a function that receives the `RootNode` and returns a string.
204
+ description: |
205
+ Text prepended to every generated file. Useful for license headers, lint disables, or `@ts-nocheck` directives.
206
+
207
+ Pass a string for a static banner. Pass a function to compute the banner from each file's `RootNode` (the AST root containing path, schema, and operation context).
208
+ examples:
209
+ - name: Static banner
210
+ files:
211
+ - name: kubb.config.ts
212
+ lang: typescript
213
+ twoslash: false
214
+ code: |
215
+ import { defineConfig } from 'kubb'
216
+ import { pluginTs } from '@kubb/plugin-ts'
217
+
218
+ export default defineConfig({
219
+ input: { path: './petStore.yaml' },
220
+ output: { path: './src/gen' },
221
+ plugins: [
222
+ pluginTs({
223
+ output: {
224
+ banner: '/* eslint-disable */\n// @ts-nocheck',
225
+ },
226
+ }),
227
+ ],
228
+ })
229
+ - name: Generated file
230
+ lang: typescript
231
+ twoslash: false
232
+ code: |
233
+ /* eslint-disable */
234
+ // @ts-nocheck
235
+ export type Pet = {
236
+ id: number
237
+ name: string
238
+ }
239
+ - name: Dynamic banner
240
+ files:
241
+ - name: kubb.config.ts
242
+ lang: typescript
243
+ twoslash: false
244
+ code: |
245
+ import { defineConfig } from 'kubb'
246
+ import { pluginTs } from '@kubb/plugin-ts'
247
+
248
+ export default defineConfig({
249
+ input: { path: './petStore.yaml' },
250
+ output: { path: './src/gen' },
251
+ plugins: [
252
+ pluginTs({
253
+ output: {
254
+ banner: (node) => `// Source: ${node.path}\n// Generated at ${new Date().toISOString()}`,
255
+ },
256
+ }),
257
+ ],
258
+ })
86
259
  - name: footer
87
- type: "string | ((node: RootNode) => string)"
260
+ type: 'string | ((node: RootNode) => string)'
88
261
  required: false
89
- description: Add a footer comment at the end of every generated file. Accepts a static string or a function that receives the `RootNode` and returns a string.
262
+ description: |
263
+ Text appended at the end of every generated file. The mirror of `banner` — use it for closing comments, re-enabling lint rules, or marker lines.
264
+
265
+ Pass a string for a static footer, or a function that receives the file's `RootNode` and returns the footer text.
266
+ examples:
267
+ - name: Re-enable lint after a banner disable
268
+ files:
269
+ - name: kubb.config.ts
270
+ lang: typescript
271
+ twoslash: false
272
+ code: |
273
+ import { defineConfig } from 'kubb'
274
+ import { pluginTs } from '@kubb/plugin-ts'
275
+
276
+ export default defineConfig({
277
+ input: { path: './petStore.yaml' },
278
+ output: { path: './src/gen' },
279
+ plugins: [
280
+ pluginTs({
281
+ output: {
282
+ banner: '/* eslint-disable */',
283
+ footer: '/* eslint-enable */',
284
+ },
285
+ }),
286
+ ],
287
+ })
90
288
  - name: override
91
289
  type: boolean
92
290
  required: false
93
- default: "false"
94
- description: Whether Kubb overrides existing external files that can be generated if they already exist.
291
+ default: 'false'
292
+ description: |
293
+ Allows the plugin to overwrite hand-written files that share a name with a generated file.
294
+
295
+ - `false` (default): Kubb skips a file if it already exists and is not marked as generated. This protects manual edits.
296
+ - `true`: Kubb overwrites any file at the target path, including hand-written ones.
297
+ warning: |
298
+ Enable this only when you are sure the target folder contains nothing you need to keep. Local edits are lost on the next generation.
299
+ examples:
300
+ - name: kubb.config.ts
301
+ files:
302
+ - lang: typescript
303
+ twoslash: false
304
+ code: |
305
+ import { defineConfig } from 'kubb'
306
+ import { pluginTs } from '@kubb/plugin-ts'
307
+
308
+ export default defineConfig({
309
+ input: { path: './petStore.yaml' },
310
+ output: { path: './src/gen' },
311
+ plugins: [
312
+ pluginTs({
313
+ output: { override: true },
314
+ }),
315
+ ],
316
+ })
95
317
  - name: resolver
96
318
  type: Partial<ResolverZod> & ThisType<ResolverZod>
97
319
  required: false
98
320
  description: |
99
- A single resolver that overrides the naming and path-resolution conventions. Each method you provide replaces the corresponding built-in one. When a method returns `null` or `undefined`, the resolver's result is used as the fallback, so you only need to supply the methods you want to change.
321
+ Overrides how the plugin builds names and paths for generated files and symbols. Use this to add prefixes, suffixes, or to swap the casing strategy without forking the plugin.
322
+
323
+ Only override the methods you want to change. Anything you omit falls back to the plugin's default resolver. A method that returns `null` or `undefined` also falls back.
100
324
 
101
- `this` inside each method is bound to the **full** resolver, so you can call other resolver methods (e.g. `this.default(name, 'function')`) without losing context.
325
+ Inside each method, `this` is bound to the full resolver, so you can call `this.default(name, 'function')` to delegate to the built-in implementation.
102
326
  body: |
103
- Each plugin ships a built-in resolver:
327
+ Each plugin ships with a default resolver:
104
328
 
105
329
  | Plugin | Default resolver |
106
330
  | --- | --- |
107
331
  | `@kubb/plugin-ts` | `resolverTs` |
108
332
  | `@kubb/plugin-zod` | `resolverZod` |
333
+ | `@kubb/plugin-faker` | `resolverFaker` |
109
334
  | `@kubb/plugin-cypress` | `resolverCypress` |
335
+ | `@kubb/plugin-msw` | `resolverMsw` |
110
336
  | `@kubb/plugin-mcp` | `resolverMcp` |
337
+ | `@kubb/plugin-client` | `resolverClient` |
111
338
  codeBlock:
112
339
  lang: typescript
113
- title: Custom resolver (plugin-ts example)
340
+ title: Add an Api prefix to every name
114
341
  code: |
342
+ import { defineConfig } from 'kubb'
115
343
  import { pluginTs } from '@kubb/plugin-ts'
116
344
 
117
- pluginTs({
118
- resolver: {
119
- resolveName(name) {
120
- // Prefix every operation-derived name; falls back for names where
121
- // this returns null/undefined.
122
- return `Api${this.default(name, 'function')}`
123
- },
124
- },
345
+ export default defineConfig({
346
+ input: { path: './petStore.yaml' },
347
+ output: { path: './src/gen' },
348
+ plugins: [
349
+ pluginTs({
350
+ resolver: {
351
+ resolveName(name) {
352
+ return `Api${this.default(name, 'function')}`
353
+ },
354
+ },
355
+ }),
356
+ ],
125
357
  })
126
358
  tip: |
127
- Use `resolver` for fine-grained control over naming conventions.
359
+ Use `resolver` for naming and file-location tweaks. For changing the AST nodes themselves (e.g. stripping descriptions), use `transformer` instead.
128
360
  - name: group
129
361
  type: Group
130
362
  required: false
131
363
  description: |
132
- Grouping combines files in a folder based on a specific `type`.
364
+ Splits generated files into subfolders based on the operation's tag, so each tag in your OpenAPI spec gets its own directory.
365
+
366
+ Without `group`, every file lands in the plugin's `output.path` folder. With `group`, files are bucketed under `{output.path}/{groupName}/`, where `groupName` is derived from the operation's first tag.
367
+ tip: |
368
+ Use `group` to mirror your API's domain structure (pet, store, user) in the generated code. Combine it with `output.barrel: { type: 'named', nested: true }` to get per-tag barrel files.
133
369
  examples:
134
370
  - name: kubb.config.ts
135
371
  files:
136
372
  - lang: typescript
137
- code: |
138
- group: {
139
- type: 'tag',
140
- name({ group }) {
141
- return `${group}Controller`
142
- }
143
- }
144
373
  twoslash: false
374
+ code: |
375
+ import { defineConfig } from 'kubb'
376
+ import { pluginTs } from '@kubb/plugin-ts'
377
+
378
+ export default defineConfig({
379
+ input: { path: './petStore.yaml' },
380
+ output: { path: './src/gen' },
381
+ plugins: [
382
+ pluginTs({
383
+ group: {
384
+ type: 'tag',
385
+ name: ({ group }) => `${group}Controller`,
386
+ },
387
+ }),
388
+ ],
389
+ })
145
390
  body: |
146
391
  With the configuration above, the generator emits:
147
392
 
148
393
  ```text
149
- .
150
- ├── src/
151
- └── petController/
152
- │ ├── addPet.ts
153
- │ │ └── getPet.ts
154
- │ └── storeController/
155
- │ ├── createStore.ts
156
- │ └── getStoreById.ts
157
- ├── petStore.yaml
158
- ├── kubb.config.ts
159
- └── package.json
394
+ src/gen/
395
+ ├── petController/
396
+ ├── AddPet.ts
397
+ └── GetPet.ts
398
+ └── storeController/
399
+ ├── CreateStore.ts
400
+ └── GetStoreById.ts
160
401
  ```
161
402
  properties:
162
403
  - name: type
163
404
  type: "'tag'"
164
405
  required: true
165
- description: Specify the property to group files by. Required when `group` is defined.
406
+ description: |
407
+ Property used to assign each operation to a group. Required whenever `group` is set.
408
+
409
+ Today only `'tag'` is supported: Kubb reads the first tag on the operation (`operation.getTags().at(0)?.name`) and uses it as the group key. Operations without a tag are placed in a default group.
166
410
  note: |
167
- `Required: true*` means this is required only when the `group` option is used. The `group` option itself is optional.
168
- body: |
169
- - `'tag'`: Uses the first tag from `operation.getTags().at(0)?.name`
411
+ `Required: true*` is conditional only required when the parent `group` option is used. `group` itself stays optional.
170
412
  - name: name
171
- type: "(context: GroupContext) => string"
413
+ type: '(context: GroupContext) => string'
172
414
  required: false
173
415
  default: (ctx) => `${ctx.group}Controller`
174
- description: Return the name of a group based on the group name. This is used for file and identifier generation.
416
+ description: Function that builds the folder/identifier name from a group key (the operation's first tag).
175
417
  - name: importPath
176
418
  type: string
177
419
  required: false
178
420
  default: "'zod'"
179
421
  description: |
180
- Import path for Zod. Use `'zod/mini'` to import from Zod's mini bundle.
422
+ Module specifier used in the `import { z } from '...'` statement at the top of generated files.
423
+
424
+ Use `'zod/mini'` to import from the tree-shakeable Mini bundle, or a custom path when re-exporting Zod from your own module.
425
+ examples:
426
+ - name: Use Zod's mini bundle
427
+ files:
428
+ - lang: typescript
429
+ twoslash: false
430
+ code: |
431
+ import { defineConfig } from 'kubb'
432
+ import { pluginZod } from '@kubb/plugin-zod'
433
+
434
+ export default defineConfig({
435
+ input: { path: './petStore.yaml' },
436
+ output: { path: './src/gen' },
437
+ plugins: [
438
+ pluginZod({ importPath: 'zod/mini' }),
439
+ ],
440
+ })
181
441
  - name: typed
182
442
  type: boolean
183
443
  required: false
184
- default: "false"
185
- description: Annotate schemas with their TypeScript types from `@kubb/plugin-ts`. Requires `@kubb/plugin-ts` in the plugins array.
444
+ default: 'false'
445
+ description: |
446
+ Adds a type annotation that ties each Zod schema to its TypeScript counterpart from `@kubb/plugin-ts`.
447
+
448
+ With `typed: true`, the generated `petSchema` is typed as `ToZod<Pet>` — TypeScript will fail compilation when the schema drifts from the type. Requires `@kubb/plugin-ts` in the plugins list.
186
449
  important: |
187
- We rely on [`ToZod`](https://github.com/colinhacks/tozod) from the creator of Zod to create a schema based on a type.
188
- Kubb contains its own version to those kind of conversions.
450
+ The mapping uses a [ToZod-style](https://github.com/colinhacks/tozod) helper (vendored in Kubb) to derive a Zod shape from a TypeScript type.
451
+ examples:
452
+ - name: Schema linked to its TS type
453
+ files:
454
+ - lang: typescript
455
+ twoslash: false
456
+ code: |
457
+ import { z } from 'zod'
458
+ import type { ToZod } from '@kubb/plugin-zod'
459
+ import type { Pet } from '../ts/Pet'
460
+
461
+ export const petSchema: ToZod<Pet> = z.object({
462
+ name: z.string(),
463
+ status: z.enum(['available', 'pending', 'sold']).optional(),
464
+ })
189
465
  - name: inferred
190
466
  type: boolean
191
467
  required: false
192
- default: "false"
193
- description: Export a `z.infer<typeof schema>` type alias for each schema to keep types in sync with validation.
468
+ default: 'false'
469
+ description: |
470
+ Exports a `z.infer<typeof schema>` type alias next to every generated schema.
471
+
472
+ Use this when you want one source of truth (the Zod schema) and a TypeScript type derived from it, instead of importing types separately from `@kubb/plugin-ts`.
194
473
  codeBlock:
195
474
  lang: typescript
196
- title: "inferred: true"
475
+ title: With inferred enabled
197
476
  code: |
198
477
  import { z } from 'zod'
199
478
 
@@ -202,301 +481,490 @@ options:
202
481
  status: z.enum(['available', 'pending', 'sold']).optional(),
203
482
  })
204
483
 
205
- // Inferred type export
206
484
  export type Pet = z.infer<typeof petSchema>
207
485
  - name: integerType
208
486
  warning: |
209
- This option has been moved to [`adapterOas`](/adapters/adapter-oas#integerType). Use `adapterOas({ integerType })` instead.
487
+ Moved to [`adapterOas`](/adapters/adapter-oas#integerType). Use `adapterOas({ integerType })` instead.
210
488
  - name: unknownType
211
489
  warning: |
212
- This option has been moved to [`adapterOas`](/adapters/adapter-oas#unknownType). Use `adapterOas({ unknownType })` instead.
490
+ Moved to [`adapterOas`](/adapters/adapter-oas#unknownType). Use `adapterOas({ unknownType })` instead.
213
491
  - name: emptySchemaType
214
492
  warning: |
215
- This option has been moved to [`adapterOas`](/adapters/adapter-oas#emptySchemaType). Use `adapterOas({ emptySchemaType })` instead.
493
+ Moved to [`adapterOas`](/adapters/adapter-oas#emptySchemaType). Use `adapterOas({ emptySchemaType })` instead.
216
494
  - name: coercion
217
- type: "boolean | { dates?: boolean, strings?: boolean, numbers?: boolean }"
495
+ type: 'boolean | { dates?: boolean, strings?: boolean, numbers?: boolean }'
218
496
  required: false
219
- default: "false"
220
- description: Apply `z.coerce` to automatically convert input values to the expected type before validation. Pass `true` to coerce all primitives, or an object to selectively enable coercion for `dates`, `strings`, and `numbers`. See [Coercion for primitives](https://zod.dev/?id=coercion-for-primitives).
497
+ default: 'false'
498
+ description: |
499
+ Wraps schemas in `z.coerce` so input is coerced to the expected type before validation. Useful for form data, query params, and any source where everything arrives as a string.
500
+
501
+ - `true` — coerce strings, numbers, and dates.
502
+ - `false` (default) — no coercion. Strict validation.
503
+ - Object — pick which primitives to coerce.
504
+
505
+ See [Coercion for primitives](https://zod.dev/?id=coercion-for-primitives).
506
+ tip: |
507
+ When `@kubb/adapter-oas` runs with `dateType: 'date'` (date fields typed as `Date`), the generated schemas round-trip dates at the validation boundary rather than coercing: response schemas decode the ISO `string` into a `Date` (`z.iso.datetime().transform(...)`), and an `${name}InputSchema` variant encodes `Date` back into an ISO `string` (`z.date().transform(...)`) for request bodies. `coercion.dates` has no effect on these fields.
221
508
  examples:
222
- - name: "true"
509
+ - name: '`coercion: true`'
223
510
  files:
224
511
  - lang: typescript
225
- code: |
226
- z.coerce.string();
227
- z.coerce.date();
228
- z.coerce.number();
229
512
  twoslash: false
230
- - name: "false"
513
+ code: |
514
+ z.coerce.string()
515
+ z.coerce.date()
516
+ z.coerce.number()
517
+ - name: '`coercion: false` (default)'
231
518
  files:
232
519
  - lang: typescript
233
- code: |
234
- z.string();
235
- z.date();
236
- z.number();
237
520
  twoslash: false
238
- - name: "{numbers: true, strings: false, dates: false}"
521
+ code: |
522
+ z.string()
523
+ z.date()
524
+ z.number()
525
+ - name: Coerce numbers only
239
526
  files:
240
527
  - lang: typescript
241
- code: |
242
- z.string();
243
- z.date();
244
- z.coerce.number();
245
528
  twoslash: false
529
+ code: |
530
+ // { numbers: true, strings: false, dates: false }
531
+ z.string()
532
+ z.date()
533
+ z.coerce.number()
246
534
  - name: operations
247
535
  type: boolean
248
536
  required: false
249
- default: "false"
250
- description: Generate an `operations.ts` file with schemas for each operation, including request and response data.
537
+ default: 'false'
538
+ description: |
539
+ Emits an `operations.ts` file that groups schemas per operation: request body, query params, path params, and each response status.
540
+
541
+ Use this to validate or describe whole operations in one place — handy when wiring Kubb output into a server framework that takes Zod schemas per route.
251
542
  - name: paramsCasing
252
543
  type: "'camelcase'"
253
544
  required: false
254
- description: Transform parameter property names to camelCase.
545
+ description: |
546
+ Renames properties inside the path/query/header schemas to the chosen casing. Body schemas are unaffected.
547
+
548
+ Must match the value of `paramsCasing` on `@kubb/plugin-ts` so the generated Zod schemas stay assignable to the generated types.
255
549
  codeBlock:
256
550
  lang: typescript
257
- title: "'camelcase'"
551
+ title: "`paramsCasing: 'camelcase'`"
258
552
  code: |
259
553
  // OpenAPI spec uses: pet_id, X-Api-Key
554
+ export const getPetPathParamsSchema = z.object({
555
+ petId: z.string(),
556
+ })
260
557
 
261
- type GetPetPathParams = {
262
- petId: string // ✓ camelCase
263
- }
264
-
265
- type GetPetHeaderParams = {
266
- xApiKey?: string // ✓ camelCase
267
- }
558
+ export const getPetHeaderParamsSchema = z.object({
559
+ xApiKey: z.string().optional(),
560
+ })
268
561
  - name: guidType
269
562
  type: "'uuid' | 'guid'"
270
563
  required: false
271
564
  default: "'uuid'"
272
565
  description: |
273
- Validator to use for OpenAPI properties with `format: uuid`. Use `'guid'` to generate `.guid()` validation instead of the default `.uuid()`.
566
+ Validator used for OpenAPI properties with `format: uuid`.
567
+
568
+ - `'uuid'` (default) — `z.uuid()`. Standard RFC 4122 UUID.
569
+ - `'guid'` — `z.guid()`. Looser; accepts Microsoft-style GUIDs (allows lowercase, mixed brace styles).
274
570
  examples:
275
571
  - name: "'uuid' (default)"
276
572
  files:
277
573
  - lang: typescript
574
+ twoslash: false
278
575
  code: |
279
576
  z.uuid()
280
- twoslash: false
281
577
  - name: "'guid'"
282
578
  files:
283
579
  - lang: typescript
580
+ twoslash: false
284
581
  code: |
285
582
  z.guid()
286
- twoslash: false
287
583
  - name: mini
288
584
  type: boolean
289
585
  required: false
290
- default: "false"
586
+ default: 'false'
291
587
  badge:
292
588
  type: tip
293
589
  text: beta
294
590
  description: |
295
- Use Zod Mini's functional API for better tree-shaking support.
591
+ Switches code generation to [Zod Mini](https://zod.dev/packages/mini). Schemas use the functional API (`z.optional(z.string())`) instead of the chainable one (`z.string().optional()`), which lets bundlers tree-shake unused validators.
296
592
 
297
- When enabled, generates functional syntax (e.g., `z.optional(z.string())`) instead of chainable methods (e.g., `z.string().optional()`).
298
-
299
- When `mini: true`, `importPath` will default to `'zod/mini'`.
593
+ Setting `mini: true` also defaults `importPath` to `'zod/mini'`.
300
594
  warning: |
301
- This feature is currently in beta. The API may change in future releases.
595
+ Zod Mini is currently in beta. Its API may change in a future release.
302
596
  tip: |
303
- Zod Mini provides a smaller bundle size with better tree-shaking. See [Zod Mini documentation](https://zod.dev/packages/mini) for more details.
597
+ Use Zod Mini in code that ships to the browser. The functional API drops several kilobytes from the bundle compared to the standard Zod build.
304
598
  examples:
305
- - name: "mini: true"
599
+ - name: '`mini: true`'
306
600
  files:
307
601
  - lang: typescript
602
+ twoslash: false
308
603
  code: |
309
- // Import from zod/mini
310
604
  import { z } from 'zod/mini'
311
605
 
312
- // Functional syntax for better tree-shaking
313
606
  z.optional(z.string())
314
607
  z.nullable(z.number())
315
608
  z.array(z.string()).check(z.minLength(1), z.maxLength(10))
316
- twoslash: false
317
- - name: "mini: false (default)"
609
+ - name: '`mini: false` (default)'
318
610
  files:
319
611
  - lang: typescript
612
+ twoslash: false
320
613
  code: |
321
- // Import from zod or zod/v4
322
614
  import { z } from 'zod'
323
615
 
324
- // Chainable method syntax
325
616
  z.string().optional()
326
617
  z.number().nullable()
327
618
  z.array(z.string()).min(1).max(10)
328
- twoslash: false
329
619
  - name: include
330
620
  type: Array<Include>
331
621
  required: false
332
- description: Array containing include parameters to include tags, operations, methods, paths, or content types.
622
+ description: |
623
+ Restricts generation to operations that match at least one entry in the list. Anything not matched is skipped.
624
+
625
+ Each entry filters by one of:
626
+
627
+ - `tag` — the operation's first tag in the OpenAPI spec.
628
+ - `operationId` — the operation's `operationId`.
629
+ - `path` — the URL pattern (`'/pet/{petId}'`).
630
+ - `method` — HTTP method (`'get'`, `'post'`, ...).
631
+ - `contentType` — the media type of the request body.
632
+
633
+ `pattern` accepts either a string (exact match) or a `RegExp` for fuzzy matches.
333
634
  codeBlock:
334
635
  lang: typescript
335
- title: Include
636
+ title: Type definition
336
637
  code: |
337
638
  export type Include = {
338
639
  type: 'tag' | 'operationId' | 'path' | 'method' | 'contentType'
339
640
  pattern: string | RegExp
340
641
  }
642
+ examples:
643
+ - name: Only the pet tag
644
+ files:
645
+ - name: kubb.config.ts
646
+ lang: typescript
647
+ twoslash: false
648
+ code: |
649
+ import { defineConfig } from 'kubb'
650
+ import { pluginTs } from '@kubb/plugin-ts'
651
+
652
+ export default defineConfig({
653
+ input: { path: './petStore.yaml' },
654
+ output: { path: './src/gen' },
655
+ plugins: [
656
+ pluginTs({
657
+ include: [
658
+ { type: 'tag', pattern: 'pet' },
659
+ ],
660
+ }),
661
+ ],
662
+ })
663
+ - name: Only GET operations under /pet
664
+ files:
665
+ - name: kubb.config.ts
666
+ lang: typescript
667
+ twoslash: false
668
+ code: |
669
+ import { defineConfig } from 'kubb'
670
+ import { pluginTs } from '@kubb/plugin-ts'
671
+
672
+ export default defineConfig({
673
+ input: { path: './petStore.yaml' },
674
+ output: { path: './src/gen' },
675
+ plugins: [
676
+ pluginTs({
677
+ include: [
678
+ { type: 'method', pattern: 'get' },
679
+ { type: 'path', pattern: /^\/pet/ },
680
+ ],
681
+ }),
682
+ ],
683
+ })
341
684
  - name: exclude
342
685
  type: Array<Exclude>
343
686
  required: false
344
- description: Array containing exclude parameters to exclude or skip tags, operations, methods, paths, or content types.
687
+ description: |
688
+ Skips any operation that matches at least one entry in the list. The opposite of `include`.
689
+
690
+ Each entry filters by one of:
691
+
692
+ - `tag` — the operation's first tag.
693
+ - `operationId` — the operation's `operationId`.
694
+ - `path` — the URL pattern (`'/pet/{petId}'`).
695
+ - `method` — HTTP method (`'get'`, `'post'`, ...).
696
+ - `contentType` — the media type of the request body.
697
+
698
+ `pattern` accepts a plain string or a `RegExp`. When both `include` and `exclude` are set, `exclude` wins.
345
699
  codeBlock:
346
700
  lang: typescript
347
- title: Exclude
701
+ title: Type definition
348
702
  code: |
349
703
  export type Exclude = {
350
704
  type: 'tag' | 'operationId' | 'path' | 'method' | 'contentType'
351
705
  pattern: string | RegExp
352
706
  }
707
+ examples:
708
+ - name: Skip everything under the store tag
709
+ files:
710
+ - name: kubb.config.ts
711
+ lang: typescript
712
+ twoslash: false
713
+ code: |
714
+ import { defineConfig } from 'kubb'
715
+ import { pluginTs } from '@kubb/plugin-ts'
716
+
717
+ export default defineConfig({
718
+ input: { path: './petStore.yaml' },
719
+ output: { path: './src/gen' },
720
+ plugins: [
721
+ pluginTs({
722
+ exclude: [
723
+ { type: 'tag', pattern: 'store' },
724
+ ],
725
+ }),
726
+ ],
727
+ })
728
+ - name: Skip a specific operation and all delete methods
729
+ files:
730
+ - name: kubb.config.ts
731
+ lang: typescript
732
+ twoslash: false
733
+ code: |
734
+ import { defineConfig } from 'kubb'
735
+ import { pluginTs } from '@kubb/plugin-ts'
736
+
737
+ export default defineConfig({
738
+ input: { path: './petStore.yaml' },
739
+ output: { path: './src/gen' },
740
+ plugins: [
741
+ pluginTs({
742
+ exclude: [
743
+ { type: 'operationId', pattern: 'deletePet' },
744
+ { type: 'method', pattern: 'delete' },
745
+ ],
746
+ }),
747
+ ],
748
+ })
353
749
  - name: override
354
750
  type: Array<Override>
355
751
  required: false
356
- description: Array containing override parameters to override `options` based on tags, operations, methods, paths, or content types.
752
+ description: |
753
+ Applies a different set of plugin options to operations that match a pattern. Use this when most of your API should follow the global config, but a handful of endpoints need different treatment.
754
+
755
+ Each entry has the same `type` and `pattern` shape as `include`/`exclude`, plus an `options` object that overrides the plugin's options for matched operations.
756
+
757
+ Entries are evaluated top to bottom. The first matching entry's `options` is merged onto the plugin defaults; later entries do not stack.
357
758
  codeBlock:
358
759
  lang: typescript
359
- title: Override
760
+ title: Type definition
360
761
  code: |
361
762
  export type Override = {
362
763
  type: 'tag' | 'operationId' | 'path' | 'method' | 'contentType'
363
764
  pattern: string | RegExp
364
765
  options: PluginOptions
365
766
  }
767
+ examples:
768
+ - name: Use a different enum style for the user tag
769
+ files:
770
+ - name: kubb.config.ts
771
+ lang: typescript
772
+ twoslash: false
773
+ code: |
774
+ import { defineConfig } from 'kubb'
775
+ import { pluginTs } from '@kubb/plugin-ts'
776
+
777
+ export default defineConfig({
778
+ input: { path: './petStore.yaml' },
779
+ output: { path: './src/gen' },
780
+ plugins: [
781
+ pluginTs({
782
+ enumType: 'asConst',
783
+ override: [
784
+ {
785
+ type: 'tag',
786
+ pattern: 'user',
787
+ options: { enumType: 'literal' },
788
+ },
789
+ ],
790
+ }),
791
+ ],
792
+ })
366
793
  - name: generators
367
794
  type: Array<Generator<PluginZod>>
368
795
  required: false
369
796
  experimental: true
370
797
  description: |
371
- Define additional generators next to the built-in generators.
798
+ Adds custom generators that run alongside the plugin's built-in generators. Each generator can emit additional files or post-process existing ones using the plugin's AST and options.
372
799
 
373
- See [Generators](https://kubb.dev/docs/5.x/guides/creating-plugins) for more information on how to use generators.
800
+ Use this when you need output the plugin does not produce out of the box (a custom client wrapper, an extra index, a metadata file). For end-to-end guidance, see [Creating plugins](https://kubb.dev/docs/5.x/guides/creating-plugins).
801
+ warning: |
802
+ Generators are an experimental, low-level API. The signature may change between minor releases.
374
803
  - name: transformer
375
804
  type: Visitor
376
805
  required: false
377
806
  description: |
378
- A single AST visitor applied to every node before code is printed. Each method you provide replaces the corresponding built-in one. When a method returns `null` or `undefined`, the preset transformer's result is used as the fallback — so you only need to supply the methods you want to change.
807
+ Modifies AST nodes before they are printed to source code. Use this when you need to rewrite operation IDs, drop descriptions, or change schema metadata without forking the generator.
379
808
 
380
- Visitor methods receive the node and a context object. Return a modified node to replace it, or return `undefined`/`void` to leave it unchanged.
809
+ Each visitor method (e.g. `schema`, `operation`) receives the node and a context object. Return a new node to replace it, or return `undefined` to leave it untouched. Methods you omit keep the plugin's default behavior.
381
810
  examples:
382
811
  - name: Strip descriptions before printing
383
812
  files:
384
- - lang: typescript
813
+ - name: kubb.config.ts
814
+ lang: typescript
815
+ twoslash: false
385
816
  code: |
817
+ import { defineConfig } from 'kubb'
386
818
  import { pluginTs } from '@kubb/plugin-ts'
387
819
 
388
- pluginTs({
389
- transformer: {
390
- schema(node) {
391
- return { ...node, description: undefined }
392
- },
393
- },
820
+ export default defineConfig({
821
+ input: { path: './petStore.yaml' },
822
+ output: { path: './src/gen' },
823
+ plugins: [
824
+ pluginTs({
825
+ transformer: {
826
+ schema(node) {
827
+ return { ...node, description: undefined }
828
+ },
829
+ },
830
+ }),
831
+ ],
394
832
  })
395
- twoslash: false
396
833
  - name: Prefix every operationId
397
834
  files:
398
- - lang: typescript
835
+ - name: kubb.config.ts
836
+ lang: typescript
837
+ twoslash: false
399
838
  code: |
839
+ import { defineConfig } from 'kubb'
400
840
  import { pluginTs } from '@kubb/plugin-ts'
401
841
 
402
- pluginTs({
403
- transformer: {
404
- operation(node) {
405
- return { ...node, operationId: `api_${node.operationId}` }
406
- },
407
- },
842
+ export default defineConfig({
843
+ input: { path: './petStore.yaml' },
844
+ output: { path: './src/gen' },
845
+ plugins: [
846
+ pluginTs({
847
+ transformer: {
848
+ operation(node) {
849
+ return { ...node, operationId: `api_${node.operationId}` }
850
+ },
851
+ },
852
+ }),
853
+ ],
408
854
  })
409
- twoslash: false
410
855
  tip: |
411
- Use `transformer` to rewrite node properties before printing. For output naming customization, use `resolver` instead.
856
+ Use `transformer` to rewrite node properties before printing. For changing the names of generated symbols and files, use `resolver` instead.
412
857
  - name: printer
413
- type: "{ nodes?: PrinterZodNodes | PrinterZodMiniNodes }"
858
+ type: '{ nodes?: PrinterZodNodes | PrinterZodMiniNodes }'
414
859
  required: false
415
860
  description: |
416
- Override individual printer node handlers to customize how specific schema types are rendered. When `mini: true` the overrides apply to the Zod Mini printer.
861
+ Replaces the Zod handler for a specific schema type (e.g. `'integer'`, `'date'`, `'string'`). Each handler returns the Zod expression as a string.
417
862
 
418
- Each key is a `SchemaType` (e.g. `'integer'`, `'date'`). The function you provide replaces the built-in handler for that type. Use `this.transform` to recurse into nested schema nodes.
863
+ When `mini: true`, overrides target the Zod Mini printer; otherwise they target the standard Zod printer.
419
864
  examples:
420
- - name: Override integer to z.number()
865
+ - name: Use z.number() for integers
421
866
  files:
422
867
  - lang: typescript
868
+ twoslash: false
423
869
  code: |
870
+ import { defineConfig } from 'kubb'
424
871
  import { pluginZod } from '@kubb/plugin-zod'
425
872
 
426
- pluginZod({
427
- printer: {
428
- nodes: {
429
- integer() {
430
- return 'z.number()'
873
+ export default defineConfig({
874
+ input: { path: './petStore.yaml' },
875
+ output: { path: './src/gen' },
876
+ plugins: [
877
+ pluginZod({
878
+ printer: {
879
+ nodes: {
880
+ integer() {
881
+ return 'z.number()'
882
+ },
883
+ },
431
884
  },
432
- },
433
- },
885
+ }),
886
+ ],
434
887
  })
435
- twoslash: false
436
- - name: Override date to z.string().date()
888
+ - name: Use z.string().date() for date schemas
437
889
  files:
438
890
  - lang: typescript
891
+ twoslash: false
439
892
  code: |
893
+ import { defineConfig } from 'kubb'
440
894
  import { pluginZod } from '@kubb/plugin-zod'
441
895
 
442
- pluginZod({
443
- printer: {
444
- nodes: {
445
- date(node) {
446
- return 'z.string().date()'
896
+ export default defineConfig({
897
+ input: { path: './petStore.yaml' },
898
+ output: { path: './src/gen' },
899
+ plugins: [
900
+ pluginZod({
901
+ printer: {
902
+ nodes: {
903
+ date() {
904
+ return 'z.string().date()'
905
+ },
906
+ },
447
907
  },
448
- },
449
- },
908
+ }),
909
+ ],
450
910
  })
451
- twoslash: false
452
911
  - name: wrapOutput
453
- type: "(arg: { output: string; schema: SchemaNode }) => string | undefined"
912
+ type: '(arg: { output: string; schema: SchemaNode }) => string | undefined'
454
913
  required: false
455
- description: Wrap the generated Zod schema string with additional validation or metadata. The callback receives the schema's output string and the `SchemaNode` AST node, and returns the modified schema string.
914
+ description: |
915
+ Lets you wrap the generated Zod schema string with extra calls before it is written to disk. The callback receives the raw schema output and the originating `SchemaNode`.
916
+
917
+ Return a new string to replace the output, or return `undefined` to leave it untouched.
456
918
  tip: |
457
- This is useful for cases where you need to extend the generated zod output with additional properties from an OpenAPI schema. E.g. in the case of `OpenAPI -> Zod -> OpenAPI`, you could include the examples from the schema for a given property and then ultimately provide a modified schema to a router that supports zod and OpenAPI spec generation.
919
+ Use this to round-trip metadata from OpenAPI back into Zod examples, descriptions, or `.openapi()` annotations for libraries that re-emit OpenAPI from Zod schemas.
458
920
  codeBlock:
459
921
  lang: typescript
460
- title: Conditionally append .openapi() to the generated schema
922
+ title: Append .openapi() with metadata
461
923
  code: |
462
- wrapOutput: ({ output, schema }) => {
463
- const metadata: Record<string, unknown> = {};
924
+ import { defineConfig } from 'kubb'
925
+ import { pluginZod } from '@kubb/plugin-zod'
464
926
 
465
- if (schema.keywords?.includes('example')) {
466
- // access SchemaNode properties
467
- }
927
+ export default defineConfig({
928
+ input: { path: './petStore.yaml' },
929
+ output: { path: './src/gen' },
930
+ plugins: [
931
+ pluginZod({
932
+ wrapOutput: ({ output, schema }) => {
933
+ const metadata: Record<string, unknown> = {}
468
934
 
469
- if (Object.keys(metadata).length > 0) {
470
- return `${output}.openapi(${JSON.stringify(metadata)})`;
471
- }
472
- };
935
+ if (schema.keywords?.includes('example')) {
936
+ // Pull keyword metadata off the SchemaNode here
937
+ }
938
+
939
+ if (Object.keys(metadata).length > 0) {
940
+ return `${output}.openapi(${JSON.stringify(metadata)})`
941
+ }
942
+
943
+ return undefined
944
+ },
945
+ }),
946
+ ],
947
+ })
473
948
  examples:
474
949
  - name: kubb.config.ts
475
950
  files:
476
951
  - lang: typescript
952
+ twoslash: false
477
953
  code: |
478
- import { defineConfig } from 'kubb';
479
- import { pluginTs } from '@kubb/plugin-ts';
480
- import { pluginZod } from '@kubb/plugin-zod';
954
+ import { defineConfig } from 'kubb'
955
+ import { pluginTs } from '@kubb/plugin-ts'
956
+ import { pluginZod } from '@kubb/plugin-zod'
481
957
 
482
958
  export default defineConfig({
483
- input: {
484
- path: './petStore.yaml',
485
- },
486
- output: {
487
- path: './src/gen',
488
- },
959
+ input: { path: './petStore.yaml' },
960
+ output: { path: './src/gen' },
489
961
  plugins: [
490
962
  pluginTs(),
491
963
  pluginZod({
492
- output: {
493
- path: './zod',
494
- },
964
+ output: { path: './zod' },
495
965
  group: { type: 'tag', name: ({ group }) => `${group}Schemas` },
496
966
  typed: true,
497
- dateType: 'stringOffset',
498
967
  importPath: 'zod',
499
968
  }),
500
969
  ],
501
- });
502
- twoslash: false
970
+ })