@kubb/plugin-zod 5.0.0-beta.15 → 5.0.0-beta.25

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