Skip to content

Migration guide: v4 → v5

Kubb v5 splits responsibilities across adapters, plugins, parsers, and storage, so the upgrade is more than a version bump. The guide runs in upgrade order: requirements, packages, config, generated-code imports, then verify. Plugin-specific changes live on each per-extension page.

TIP

In a hurry? Run the upgrade prompt against your config to migrate most of it automatically, then read on to verify the result.

Before you start

System requirements

Version v4 v5
Node.js ≥ 18 ≥ 22

Update your CI pipelines, the engines field in package.json, and any Dockerfile FROM node lines. See Installation for the full setup.

Install the v5 packages

In v4, the code-generating plugins lived in kubb-labs/kubb. v5 moves them into kubb-labs/plugins. The npm package names stay the same, so you do not need to rename anything. The infrastructure packages that kubb wires in for you, @kubb/adapter-oas, @kubb/parser-ts, @kubb/parser-md, and @kubb/plugin-barrel, stay in kubb-labs/kubb.

shell
bun add -d @kubb/plugin-ts@beta @kubb/plugin-zod@beta @kubb/plugin-axios@beta @kubb/plugin-fetch@beta \
            @kubb/plugin-react-query@beta @kubb/plugin-vue-query@beta @kubb/plugin-swr@beta \
            @kubb/plugin-faker@beta @kubb/plugin-msw@beta \
            @kubb/plugin-mcp@beta @kubb/plugin-cypress@beta @kubb/plugin-redoc@beta
shell
pnpm add -D @kubb/plugin-ts@beta @kubb/plugin-zod@beta @kubb/plugin-axios@beta @kubb/plugin-fetch@beta \
            @kubb/plugin-react-query@beta @kubb/plugin-vue-query@beta @kubb/plugin-swr@beta \
            @kubb/plugin-faker@beta @kubb/plugin-msw@beta \
            @kubb/plugin-mcp@beta @kubb/plugin-cypress@beta @kubb/plugin-redoc@beta
shell
npm install -D @kubb/plugin-ts@beta @kubb/plugin-zod@beta @kubb/plugin-axios@beta @kubb/plugin-fetch@beta \
               @kubb/plugin-react-query@beta @kubb/plugin-vue-query@beta @kubb/plugin-swr@beta \
               @kubb/plugin-faker@beta @kubb/plugin-msw@beta \
               @kubb/plugin-mcp@beta @kubb/plugin-cypress@beta @kubb/plugin-redoc@beta
shell
yarn add -D @kubb/plugin-ts@beta @kubb/plugin-zod@beta @kubb/plugin-axios@beta @kubb/plugin-fetch@beta \
            @kubb/plugin-react-query@beta @kubb/plugin-vue-query@beta @kubb/plugin-swr@beta \
            @kubb/plugin-faker@beta @kubb/plugin-msw@beta \
            @kubb/plugin-mcp@beta @kubb/plugin-cypress@beta @kubb/plugin-redoc@beta

Removed plugins

These plugins have no v5 equivalent. Remove them from your config and uninstall the packages.

v4 package
@kubb/plugin-solid-query
@kubb/plugin-svelte-query

Pick your upgrade path

Two ways to migrate kubb.config.ts: run the automated prompt, or follow the manual checklist. Either way, Migrate the config explains each change and verify confirms the result.

Automated: the upgrade prompt

Copy the prompt below, paste it into any LLM (Claude, ChatGPT, Gemini, …), and add your kubb.config.ts at the end.

Expand upgrade prompt
Upgrade prompt
text
Migrate this kubb.config.ts from Kubb v4 to v5. Apply every rule, output the full updated file, and delete any import left unused after a removal.

Config:
1. Import `defineConfig` from `kubb/config`; if the config uses `memoryStorage`/`fsStorage`, import them from `kubb/kit`.
2. `input: { path }` / `input: { data }` → a single `input` value (path, URL, spec, or object).
3. `hooks.done` → `output.postGenerate` (array); drop top-level `hooks`. Move `output.storage` → top-level `storage`.
4. Remove `pluginOas()`; move its options to top-level `adapter: adapterOas({…})` from `@kubb/adapter-oas` (omit `adapter` if it had none — it defaults to `adapterOas()`). `serverIndex`/`serverVariables` → `server: { index, variables }`; `discriminator` `'strict'`→`'preserve'`, `'inherit'`→`'propagate'`.
5. Move onto `adapterOas` (one value each; if plugins disagreed, keep the value you want): `dateType`, `integerType`, `unknownType`, `emptySchemaType`, `enumSuffix`, `contentType`.

Output:
6. `output.barrelType` → `output.barrel`, at root and per plugin: `'named'`→`{type:'named'}`, `'all'`→`{type:'all'}`, `'propagate'`→`{type:'named',nested:true}`, `false`→`false`. v5 defaults `output.barrel` to `false`, so add `barrel:{type:'named'}` at the root if v4 relied on the old `'named'` default.
7. On each plugin, an `output.path` ending in `.ts` keeps the default `mode:'file'`; a folder path needs `mode:'directory'`. The root `output` takes no `mode`.
8. `output.format`/`output.lint` now default to `false` (were `'prettier'`/`'auto'`); keep any explicit value.

Remove `output.override` (root and per-plugin). Remove from every plugin: `generators`, `bundle`, `paramsType`, `pathParamsType`, `paramsCasing`, `dataReturnType`, `urlType`, `importPath`, `mapper` — and on plugin-swr also `mutation.paramsToTrigger`.

Plugins:
9. `transformers.name` → `resolver: { name(name) { … } }`, keeping your original logic and calling the plugin's exported resolver preset for the default casing (import it: `resolverTs` from `@kubb/plugin-ts`, `resolverZod` from `@kubb/plugin-zod`, …; `this.default.name` is the plain camelCase default). `transformers.schema` → `macros: [{ name: '<label>', schema(node) { … } }]` (name each macro).
10. plugin-zod: remove `version` and `typed`; drop `wrapOutput`, leaving a `// TODO: reintroduce wrapOutput via a printer override` comment; bump `zod` to `^4`.
11. Clients. `@kubb/plugin-client` is removed → `pluginAxios` (`@kubb/plugin-axios`, when old `client` was `'axios'`/unset) or `pluginFetch` (`@kubb/plugin-fetch`, when `'fetch'`), dropping `client`; `clientType: 'class'` → `sdk: {}` and `wrapper: { name }` → `sdk: { name }` (merge into one `sdk`). react-query/vue-query/swr/mcp no longer embed a client: if the v4 `client` was an object, register one `pluginAxios`/`pluginFetch` in `plugins[]` (shared by all consumers), move its `baseURL` there, and set `client: 'axios' | 'fetch'` on the query/mcp plugin (optional when only one client plugin is registered). On plugin-axios/plugin-fetch rename `parser` → `validator` (`false` | `'zod'` | `{ request, response }`; `'client'` → `false`); if a query plugin had `parser`, put `validator: 'zod'` on the client plugin. Leave plugin-msw `parser` (`'data'`/`'faker'`) unchanged.
12. Remove `pluginSolidQuery` (`@kubb/plugin-solid-query`) and `pluginSvelteQuery` (`@kubb/plugin-svelte-query`) — no v5 equivalent.
13. Keep every other option unchanged (group, include, exclude, override array, infinite, suspense, query, mutation, coercion, mini, seed, handlers, …).

Now migrate the following kubb.config.ts:

Manual: the quick-path checklist

The highest-impact edits, in order. Each step links to its full explanation below.

  1. Bump Node to 22 and install the v5 packages at @beta.
  2. Change the import from @kubb/core to kubb/config.
  3. Give input a single value instead of { path } / { data }.
  4. Remove pluginOas() and move its options to adapter: adapterOas(...), along with the schema options that lived on each plugin.
  5. Replace pluginClient with pluginAxios or pluginFetch, and point query and MCP plugins at a client with client: 'axios' | 'fetch'.
  6. Convert output.barrelType to output.barrel, and add barrel: { type: 'named' } if you relied on the old default.
  7. Add mode: 'directory' to every plugin that writes a folder.
  8. Replace transformers.name with resolver and transformers.schema with macros; remove mapper and generators.
  9. Move hooks.done to output.postGenerate and storage to the top level.
  10. Bump the zod dependency to ^4 (see Migration: @kubb/plugin-zod).
  11. Opt back into formatting, linting, and a barrel if you want them, since all three now default to off.

Defaults that changed

The most dangerous changes are silent: a default flips, so the same config produces different output. Each row links to the full explanation and a before/after example.

Option v4 default v5 default Details
output.format 'prettier' false Formatting and linting are off by default
output.lint 'auto' false Formatting and linting are off by default
output.barrel (barrelType in v4) 'named' false output.barrelTypeoutput.barrel
output.mode Guessed from the output.path extension 'file' Set output.mode for folder output
group: { type: 'tag' } folder name <tag>Controller <tag> Group folders drop the Controller suffix
pluginReactQuery/pluginVueQuery hooks true false @kubb/plugin-react-query, @kubb/plugin-vue-query
pluginAxios/pluginFetch throwOnError Errors returned on the result, never thrown true @kubb/plugin-client removed
adapterOas integerType 'number' 'bigint' Move schema options to the adapter
pluginSwr mutation trigger shape (mutation.paramsToTrigger in v4) Off, option opt-in Always on, option removed @kubb/plugin-swr

Migrate the config

Every change to kubb.config.ts, grouped by the part of the config it touches. Work top to bottom.

Import defineConfig from kubb/config

Import defineConfig from the top-level kubb package. That package wires up the OpenAPI adapter, the TypeScript parsers, and the barrel plugin for you.

typescript
import { defineConfig } from '@kubb/core'
typescript
import { 
defineConfig
} from 'kubb/config'

Give input a single value

The two-shape input object collapses into a single value: a file path, a URL, an inline spec, or a parsed object. Kubb works out which one it is.

v4 (old) v5 (new)
input: { path: './api.yaml' } input: './api.yaml'
input: { data: spec } input: spec
kubb.config.ts
diff
export default defineConfig({
  input: { path: './petstore.yaml' },
  input: './petstore.yaml',
  output: { path: './src/gen' },
})

Adopt the layered keys

v5 adds three top-level keys that replace behavior each plugin used to carry itself. Importing from kubb applies all three defaults, so set them only to change the defaults.

Option Package Purpose Default
adapter @kubb/adapter-oas Parses the input spec into a universal AST. adapterOas()
parsers @kubb/parser-ts, @kubb/parser-md Converts AST nodes to .ts, .tsx, and .md files. [parserTs(), parserTsx(), parserMd()]
plugins (post) @kubb/plugin-barrel Post-processes output, like barrel files. [pluginBarrel()]

Move pluginOas options to the adapter

pluginOas() no longer belongs in plugins. Its options move to the top-level adapter key.

typescript
import { defineConfig } from '@kubb/core'
import { pluginOas } from '@kubb/plugin-oas'
import { pluginTs } from '@kubb/plugin-ts'

export default defineConfig({
  input: './petstore.yaml',
  output: { path: './src/gen' },
  plugins: [
    pluginOas({
      validate: true,
      serverIndex: 0,
      serverVariables: { env: 'prod' },
      discriminator: 'inherit',
    }),
    pluginTs(),
  ],
})
typescript
import { 
defineConfig
} from 'kubb/config'
import {
adapterOas
} from '@kubb/adapter-oas'
import {
pluginTs
} from '@kubb/plugin-ts'
export default
defineConfig
({
input
: './petstore.yaml',
output
: {
path
: './src/gen' },
adapter
:
adapterOas
({
validate
: true,
server
: {
index
: 0,
variables
: {
env
: 'prod' } },
discriminator
: 'propagate',
}),
plugins
: [
pluginTs
()],
})

The discriminator values were also renamed: 'strict''preserve' (now the default) and 'inherit''propagate'.

NOTE

Uninstall @kubb/plugin-oas. The adapter defaults to adapterOas() when importing from kubb, so the adapter: line is only required when you pass options.

Move schema options to the adapter

Several schema-level options that v4 accepted on each plugin now live once on adapterOas: dateType, integerType, unknownType, and emptySchemaType (from plugin-ts, plugin-zod, plugin-faker), enumSuffix (plugin-ts), and contentType (plugin-ts, plugin-zod, plugin-msw). Remove them from every plugin and set them once on the adapter.

IMPORTANT

The integerType default changed from 'number' to 'bigint', so OpenAPI int64 fields now map to bigint. Set integerType: 'number' on adapterOas to keep the old output.

The adapter page has the full table and before/after example.

Formatting and linting are off by default

output.format and output.lint both default to false in v5, so generation skips formatting and linting unless you opt in (v4 defaulted format to 'prettier' and lint to 'auto'). The accepted values are unchanged, but 'auto' now prefers the oxc tools first. See output.format and output.lint for the full value lists and detection order.

Option v4 default v5 default
output.format 'prettier' false
output.lint 'auto' false

output.barrelTypeoutput.barrel

The string barrelType option becomes an object barrel option with a type field. At the plugin level, a nested flag replaces the old 'propagate' string.

v4 (old) output.barrelType v5 (new) output.barrel
'named' { type: 'named' }
'all' { type: 'all' }
'propagate' (plugin only) { type: 'named', nested: true }
false false

IMPORTANT

output.barrel also defaults to false, where v4's barrelType defaulted to 'named'. If your v4 config relied on that default, add output.barrel: { type: 'named' } to keep the barrel, or drop it since each plugin's output is already directly importable.

typescript
import { defineConfig } from '@kubb/core'

export default defineConfig({
  input: './petstore.yaml',
  output: { path: './src/gen', barrelType: 'named' },
})
typescript
import { 
defineConfig
} from 'kubb/config'
export default
defineConfig
({
input
: './petstore.yaml',
output
: {
path
: './src/gen',
barrel
: {
type
: 'named' } },
})

The same change applies at the plugin level, on that plugin's own output:

typescript
import { defineConfig } from '@kubb/core'
import { pluginTs } from '@kubb/plugin-ts'

export default defineConfig({
  input: './petstore.yaml',
  output: { path: './src/gen' },
  plugins: [pluginTs({ output: { barrelType: 'propagate' } })],
})
typescript
import { defineConfig } from 'kubb/config'
import { pluginTs } from '@kubb/plugin-ts'

export default defineConfig({
  input: './petstore.yaml',
  output: { path: './src/gen' },
  plugins: [pluginTs({ output: { barrel: { type: 'named', nested: true } } })],
})

See @kubb/plugin-barrel for the full barrel option reference.

Set output.mode for folder output

v4 guessed the layout from the output.path extension: .ts meant one file, anything else a folder. v5 drops the guess, so state the layout with output.mode.

output.mode Layout
'file' One file for the whole plugin. The default.
'directory' One file per operation or schema.

A .ts output.path needs no change, since mode: 'file' is the default. A folder output.path needs mode: 'directory' added, or the output silently consolidates into one file.

typescript
import { defineConfig } from '@kubb/core'
import { pluginTs } from '@kubb/plugin-ts'

export default defineConfig({
  input: './petstore.yaml',
  output: { path: './src/gen' },
  plugins: [pluginTs({ output: { path: 'types' } })],
})
typescript
import { 
defineConfig
} from 'kubb/config'
import {
pluginTs
} from '@kubb/plugin-ts'
export default
defineConfig
({
input
: './petstore.yaml',
output
: {
path
: './src/gen' },
plugins
: [
pluginTs
({
output
: {
path
: 'types',
mode
: 'directory' } })],
})

mode: 'file' forbids group, since a single file has nothing to group, and pairing them stops the build with KUBB_INVALID_PLUGIN_OPTIONS. To organize 'directory' output into per-tag or per-path subfolders, keep mode: 'directory' and add group (covered next).

Group folders drop the Controller suffix

With group: { type: 'tag' }, each tag now writes to a folder named after the camelCased tag. v4 appended a Controller suffix (Requests for Cypress and MCP), so pet operations landed in petController/. v5 uses pet/ instead, and nothing else changes: your config stays the same, only the output folders do.

Output folders
text
v4: src/gen/clients/petController/  →  v5: src/gen/clients/pet/

To keep the v4 layout, set group.name on the plugin:

v5 kubb.config.ts
typescript
import { defineConfig } from 'kubb/config'
import { pluginAxios } from '@kubb/plugin-axios'

export default defineConfig({
  input: './petstore.yaml',
  output: { path: './src/gen' },
  plugins: [
    pluginAxios({
      output: { mode: 'directory' },
      group: { type: 'tag', name: ({ group }) => `${group}Controller` },
    }),
  ],
})

group requires output.mode: 'directory', since v5 now defaults to 'file'.

Replace output.override with storage

The output.override boolean is gone from the root and every plugin's output. It was meant to skip existing files, but v5 never read it, so remove it.

To keep certain files from being written, and to move storage from output.storage to the top-level storage key, supply a custom storage that no-ops setItem for the paths you protect:

kubb.config.ts
diff
 import { defineConfig } from 'kubb/config'
import { fsStorage } from 'kubb/kit'

const base = fsStorage()
const protectedPaths = ['src/gen/.kubb/client.ts']

export default defineConfig({
  input: './petStore.yaml',
  output: { path: './src/gen', override: false },
  output: { path: './src/gen' },
  storage: {
    ...base,
    async setItem(path, source) {
      if (protectedPaths.some((p) => path.endsWith(p))) return
      return base.setItem(path, source)
    },
  },
  plugins: [],
})

Import memoryStorage and fsStorage from kubb/kit, not @kubb/core.

Move hooks.done to output.postGenerate

The top-level hooks option is gone. Move its done commands into output.postGenerate. A single string becomes a one-item array, and { name, command } labels a step in the CLI output. The failure diagnostic is renamed KUBB_HOOK_FAILEDKUBB_POST_GENERATE_FAILED.

kubb.config.ts
diff
export default defineConfig({
  input: './petstore.yaml',
  output: {
    path: './src/gen',
    postGenerate: ['npm run typecheck'],
  },
  hooks: {
    done: ['npm run typecheck'],
  },
})

--debug becomes reporters

The --debug flag and the debug value of --logLevel are gone. v5 renders a run through reporters instead, registered by the config reporters key (defineConfig registers the three built-ins for you) and selected at the CLI with --reporter, defaulting to cli. The file reporter replaces --debug, writing the same kind of log to .kubb/kubb-<name>-<timestamp>.log. See Reporters for what cli, json, and file each output.

Terminal
diff
kubb generate --debug
kubb generate --reporter file

The kubb:debug hook and the createDebugger helper go away with the flag. See kubb generate for the full flag list and Diagnostics for the structured problem model the reporters render.

Update the shared plugin API

These changes apply to every plugin that used transformers in v4, plus the way query and MCP plugins reach a client.

transformers.name becomes resolver

A typed resolver replaces the single transformers.name(name, type) callback. Every plugin exposes a top-level name(name) method that sets identifier casing, so resolver: { name(name) { … } } is the shape for @kubb/plugin-ts, @kubb/plugin-zod, @kubb/plugin-axios, @kubb/plugin-fetch, @kubb/plugin-react-query, @kubb/plugin-vue-query, @kubb/plugin-swr, @kubb/plugin-msw, @kubb/plugin-faker, @kubb/plugin-cypress, and @kubb/plugin-mcp. Plugins that emit multiple symbols per operation add more resolver methods, listed on each plugin's reference page.

Call the plugin's exported preset (resolverTs.name, resolverZod.name, and so on) to keep the default casing. See Override a resolver for renaming and relocating generated files.

After porting a transformers.name callback, verify the generated identifiers: a v5 resolver can receive a differently-cased input than v4, so a custom transform may produce different names.

typescript
pluginTs({
  transformers: {
    name: (name) => `Api${name}`,
  },
})
typescript
import { 
pluginTs
,
resolverTs
} from '@kubb/plugin-ts'
pluginTs
({
resolver
: {
name
(
name
) {
return `Api${
resolverTs
.
name
(
name
)}`
}, }, })

transformers.schema becomes macros

Schema-level transformations move to macros.

typescript
pluginZod({
  transformers: {
    schema: (schema) => ({ ...schema, description: undefined }),
  },
})
typescript
import { 
pluginZod
} from '@kubb/plugin-zod'
pluginZod
({
macros
: [
{
name
: 'strip-descriptions',
schema
(
node
) {
return { ...
node
,
description
:
undefined
}
}, }, ], })

mapper is removed

The mapper option is gone from every code-generating plugin. Override a node renderer with the printer option or rewrite nodes with macros instead. See Override a printer.

generators is removed

The generators plugin option is gone. To add custom output, build your own plugin. See Creating plugins.

Query and MCP plugins select a client

In v4, the client option on pluginReactQuery, pluginVueQuery, pluginSwr, and pluginMcp was an object that configured a bundled client (clientType, dataReturnType, baseURL, bundle, importPath). In v5 those plugins call a registered client plugin instead, so client is a single string that names it: 'axios' or 'fetch'. Register @kubb/plugin-axios or @kubb/plugin-fetch, set baseURL there, and point client at it. The client plugin returns the response body, so the generated code reads res.data and there is no dataReturnType. When exactly one client plugin is registered, Kubb auto-detects it and the string is optional. Each plugin's page shows the before/after for its own config.

Per-extension changes

Open the page for each extension you use. The table lists the headline change so you can tell at a glance whether a plugin needs work.

Extension Headline change
@kubb/adapter-oas Schema options moved here; integerType default is now 'bigint'; discriminator values renamed.
@kubb/plugin-ts Request params grouped into one *Options object; enum options restructured; mapper/paramsCasing removed.
@kubb/plugin-zod Zod v4 only; typedinferred; wrapOutput/operations/mapper removed; response schema names gain a Status<code> segment.
@kubb/plugin-faker paramsCasing/mapper removed; createX is now generic over its return type.
@kubb/plugin-client (removed) Migrate to @kubb/plugin-axios or @kubb/plugin-fetch; clientTypesdk; parservalidator; throwOnError defaults to true.
@kubb/plugin-react-query client is a string selector; hooks defaults to false; params grouped; parser removed.
@kubb/plugin-vue-query Same as react-query, with hook arguments wrapped in MaybeRefOrGetter.
@kubb/plugin-swr client is a string selector; mutation.paramsToTrigger removed; requests key off shouldFetch.
@kubb/plugin-msw No config changes; handlers are typed against the request body and headers.
@kubb/plugin-cypress Request params grouped into one *Options object; dataReturnType removed; HTTP methods uppercased.
@kubb/plugin-mcp client is a string selector; now depends on plugin-ts and plugin-zod; paramsCasing removed.

Generated output

v5 also changes what the generators emit, so update code that imports the generated files. Two changes apply to every generator:

  • The banner (/* Generated by Kubb */) is set by output.defaultBanner on the root config (default 'simple'). Override it per plugin with output.banner and output.footer, each taking a string or a per-file function.
  • Response status types now carry a Status<code> suffix.

Operations that declare more than one requestBody content type now generate one type per content type plus a union alias, and the generated client takes a typed contentType argument. v4 used only the first content type, so single-content-type operations are unchanged.

The output changes specific to each generator live on its per-extension page.

Complete before/after example

A realistic multi-plugin config. Expand it to see every change from this guide applied together.

Show the full v4 → v5 config
typescript
import { defineConfig, memoryStorage } from '@kubb/core'
import { pluginOas } from '@kubb/plugin-oas'
import { pluginTs } from '@kubb/plugin-ts'
import { pluginZod } from '@kubb/plugin-zod'
import { pluginAxios } from '@kubb/plugin-axios'
import { pluginReactQuery } from '@kubb/plugin-react-query'
import { pluginFaker } from '@kubb/plugin-faker'

export default defineConfig({
  input: './petstore.yaml',
  output: {
    path: './src/gen',
    format: 'prettier',
    storage: memoryStorage(), // → top-level `storage`
  },
  plugins: [
    pluginOas({
      // → top-level `adapter` with adapterOas()
      validate: true,
      serverIndex: 0,
      discriminator: 'inherit',
    }),
    pluginTs({
      output: { path: 'types' },
      dateType: 'date', // → adapterOas
      integerType: 'number', // → adapterOas
      unknownType: 'unknown', // → adapterOas
      enumSuffix: 'enum', // → adapterOas
      mapper: {}, // removed (use printer or macros)
      transformers: {
        name: (name) => `Api${name}`,
      },
    }),
    pluginZod({
      output: { path: 'zod' },
      version: '3', // removed (always Zod v4 in v5)
      dateType: 'date', // → adapterOas
      integerType: 'number', // → adapterOas
      mapper: {}, // removed
    }),
    pluginAxios({
      output: { path: 'clients' },
    }),
    pluginReactQuery({
      output: { path: 'hooks' },
      client: 'axios',
    }),
    pluginFaker({
      output: { path: 'mocks' },
      dateType: 'date', // → adapterOas
      integerType: 'number', // → adapterOas
    }),
  ],
})
typescript
import { 
defineConfig
} from 'kubb/config'
import {
memoryStorage
} from 'kubb/kit'
import {
adapterOas
} from '@kubb/adapter-oas'
import {
pluginTs
,
resolverTs
} from '@kubb/plugin-ts'
import {
pluginZod
} from '@kubb/plugin-zod'
import {
pluginAxios
} from '@kubb/plugin-axios'
import {
pluginReactQuery
} from '@kubb/plugin-react-query'
import {
pluginFaker
} from '@kubb/plugin-faker'
export default
defineConfig
({
input
: './petstore.yaml',
output
: {
path
: './src/gen',
format
: 'prettier',
barrel
: {
type
: 'named' },
},
storage
:
memoryStorage
(),
adapter
:
adapterOas
({
validate
: true,
server
: {
index
: 0 },
discriminator
: 'propagate',
dateType
: 'date',
integerType
: 'number',
unknownType
: 'unknown',
enumSuffix
: 'enum',
}),
plugins
: [
pluginTs
({
output
: {
path
: 'types',
mode
: 'directory' },
resolver
: {
name
(
name
) {
return `Api${
resolverTs
.
name
(
name
)}`
}, }, }),
pluginZod
({
output
: {
path
: 'zod',
mode
: 'directory' },
}),
pluginAxios
({
output
: {
path
: 'clients',
mode
: 'directory' },
}),
pluginReactQuery
({
output
: {
path
: 'hooks',
mode
: 'directory' },
client
: 'axios',
}),
pluginFaker
({
output
: {
path
: 'mocks',
mode
: 'directory' },
}), ], })

Every plugin adds mode: 'directory' to keep v4's one-file-per-operation layout. Omit it to consolidate into a single file, the v5 default. The root output.barrel is set explicitly to keep v4's barrel.

Verify the upgrade

Once the config compiles, confirm the output:

  • Run kubb generate and review the diff against your previous output.
  • Type-check the generated code, and wire that check into output.postGenerate so it runs on every generate.
  • Watch for a KUBB_INVALID_PLUGIN_OPTIONS error, which usually means a plugin pairs mode: 'file' with group.
  • Update imports of renamed symbols: response types now carry a Status<code> suffix, and Zod inferred types end in Type.

Performance

v5 generates code faster than v4, and the gap widens as the spec grows. The numbers below come from scripts/benchmark/v4-vs-v5 in the kubb repository, a harness anyone can run and reproduce. It benchmarks @kubb/[email protected] against the v5 beta (@kubb/[email protected], plugins at 5.0.0-beta.103), median of three runs per configuration, both versions writing to a fresh directory so the comparison stays apples-to-apples.

up to 5×

faster generation on the OpenAI spec

petStore.yaml 21 operations +140%
kubb v4
954 ms
kubb v5
398 ms
twitter.json 80 operations +254%
kubb v4
5,928 ms
kubb v5
1,674 ms
openai.yaml 281 operations +430%
kubb v4
31,205 ms
kubb v5
5,887 ms
Types, an Axios client, Zod schemas, and Faker mocks, median of three runs, both versions writing to disk.

Benchmark machine

OS Linux 6.18.5
CPU Intel(R) Xeon(R) Processor @ 2.10GHz
Cores 4
Memory 15.7 GB
Node v22.22.2

NOTE

Absolute milliseconds and megabytes are hardware-dependent. Treat the speedup and memory-reduction percentages as the portable takeaway.

petStore.yaml, 21 operations

Plugins v4 time v5 time Speedup v4 memory v5 memory Memory
plugin-ts 579 ms 345 ms +68% 13.0 MB 23.4 MB -80%
plugin-ts + plugin-axios 630 ms 384 ms +64% 15.8 MB 24.5 MB -55%
plugin-ts + plugin-axios + plugin-zod + plugin-faker 954 ms 398 ms +140% 19.5 MB 25.1 MB -29%

twitter.json, 80 operations, 374 KB

Plugins v4 time v5 time Speedup v4 memory v5 memory Memory
plugin-ts 2,993 ms 1,071 ms +179% 110.6 MB 60.2 MB +46%
plugin-ts + plugin-axios 3,560 ms 1,160 ms +207% 115.5 MB 59.7 MB +48%
plugin-ts + plugin-axios + plugin-zod + plugin-faker 5,928 ms 1,674 ms +254% 179.3 MB 68.5 MB +62%

openai.yaml, 281 operations, 2.7 MB (openai/openai-openapi)

Plugins v4 time v5 time Speedup v4 memory v5 memory Memory
plugin-ts 14,775 ms 3,600 ms +310% 442.4 MB 146.3 MB +67%
plugin-ts + plugin-axios 16,611 ms 3,891 ms +327% 501.8 MB 148.0 MB +70%
plugin-ts + plugin-axios + plugin-zod + plugin-faker 31,205 ms 5,887 ms +430% 898.0 MB 149.3 MB +83%

The gap widens on bigger specs. In v4, every plugin bootstrapped its own pluginOas instance, so parsing ran once per plugin. In v5, adapterOas parses the spec once and shares the result across all plugins.

See also

  • Adapters: how the OpenAPI input is parsed into the universal AST.
  • Plugins: lifecycle, generators, and resolvers.
  • Parsers: how AST nodes become source files.
  • Override a resolver: rename symbols and files through the resolver option.
  • Override a printer: change the code a plugin emits for a schema type.
  • Barrel files: barrel file generation with @kubb/plugin-barrel.
  • Storage: switching between filesystem and in-memory storage.
  • @kubb/adapter-oas: every option that moved here from the plugins.
  • Plugin registry: the full list of v5 plugins.
  • Recipes: copy-paste configurations for common scenarios.