Skip to content

← Blog

Published: 2026-05-26

Release of Kubb 5.0

Introducing Kubb v5

Kubb generates TypeScript types, an Axios or Fetch client, Zod schemas, TanStack Query hooks, Faker mocks, and MSW handlers from an OpenAPI spec. v5 rewrites how it does that. One adapter parses your spec into a universal AST, plugins walk that AST, parsers turn the result into source code, and storage writes it to disk.

You feel that rewrite in three places: your config gets shorter, your generated client calls change shape, and generation runs up to six times faster. The sections below show each one. For a before and after on every breaking change, read the migration guide.

up to 6×
faster than v4
1
shared AST, parsed once
242
OpenAI operations in 2.5s

Up to 6x faster generation

The OpenAI spec is 242 operations across 2.7 MB of YAML. Generating types, an Axios client, Zod schemas, and Faker mocks from it took 14.9 seconds in v4. v5 does the same work in 2.5 seconds.

up to 6.1×

faster generation on the OpenAI spec

petStore.yaml 19 operations +235%
v4
332 ms
v5
99 ms
twitter.json 80 operations +322%
v4
2,997 ms
v5
711 ms
openai.yaml 242 operations +507%
v4
14,943 ms
v5
2,461 ms
Types, an Axios client, Zod schemas, and Faker mocks, generated on a 4-core Intel Xeon with file writing disabled.

Each run uses plugin-ts with the Axios, Zod, and Faker plugins, on a 4-core Intel Xeon with file writing disabled, so the numbers reflect the pipeline alone.

Two changes explain the gap. In v4 every plugin bootstrapped its own OpenAPI parser, so a config with four plugins read the same spec four times. In v5 the adapter parses it once and hands the same AST to every plugin. The renderer changed too: v5 prints synchronously and drops the React runtime that v4 loaded to render files. The larger the spec, the more that parse-once saving compounds, which is why the OpenAI numbers move the most.

The full tables, one per plugin combination, live in the migration guide.

A smaller config

v4 read the spec through a pluginOas entry in the plugins array, and every schema plugin repeated the same schema options like dateType and integerType. v5 moves spec reading to a top-level adapter and the schema options along with it, so each plugin keeps only what it generates.

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

export default defineConfig({
  input: { path: './petStore.yaml' },
  output: { path: './src/gen' },
  plugins: [
    pluginOas({ validate: true }),
    pluginTs({ dateType: 'date', integerType: 'number' }),
    pluginFaker({ dateType: 'date', integerType: 'number' }),
  ],
})
typescript
import { defineConfig } from 'kubb/config'
import { adapterOas } from '@kubb/adapter-oas'
import { pluginTs } from '@kubb/plugin-ts'
import { pluginFaker } from '@kubb/plugin-faker'

export default defineConfig({
  input: './petStore.yaml',
  output: { path: './src/gen' },
  adapter: adapterOas({ validate: true, dateType: 'date', integerType: 'number' }),
  plugins: [pluginTs(), pluginFaker()],
})

defineConfig now comes from the kubb/config subpath. It pre-wires the OpenAPI adapter, the TypeScript and Markdown parsers, and the barrel plugin, so the adapter line above is only needed when you pass options. input also collapsed to a single value: give it a path, a URL, an inline spec, or a parsed object, and Kubb works out which it is.

One pipeline, from spec to files

Every run flows through one pipeline, and the easiest way to see it is to follow a single getPet operation. The adapter reads it from the spec and turns it into an AST node that no longer mentions OpenAPI. The plugins walk that node in order: @kubb/plugin-ts writes the Pet type, @kubb/plugin-axios writes the getPet function that imports it, @kubb/plugin-zod writes the schema. A parser prints each file, @kubb/parser-ts for the TypeScript output and the new @kubb/parser-md for Markdown straight from the spec. Storage writes them out and skips any file that has not changed.

The adapter is the only layer that knows the source was OpenAPI, so the same plugins would run unchanged on an AsyncAPI or GraphQL adapter later. Every plugin in the suite, from TypeScript and Zod to MSW and TanStack Query, runs on this pipeline.

What changes in your generated client

Two changes reach every client call. Functions used to spread parameters across positional arguments. In v5 they take one grouped object, { body, path, query, headers }, with camelCase names that Kubb maps back to the wire names for you. The client also returns a RequestResult of { data, error, request, response }, and throwOnError defaults to true, so the common path is a destructure:

Calling the client
diff
 const pet = await getPet(1)
 const { data: pet } = await getPet({ path: { petId: 1 } })

Set throwOnError: false when you would rather read error and response.status off the result than catch. Operations that declare more than one request body content type now generate one type per content type plus a union alias, where v4 kept only the first.

Upgrading

The migration guide carries a before and after for every change, in the core config and in each plugin. It opens with an upgrade prompt: paste it into any LLM along with your kubb.config.ts and it handles most of the config for you. Check your Node version first, since v5 needs version 22 or later.

If you hit anything unexpected, open an issue on GitHub.

Kubb is open source and community-driven. If v5 helps your team, consider sponsoring the project.

👋🏽 Stijn Van Hulle