Beta You're reading the docs for Kubb v5, which is currently in beta. View the stable v4 docs
Skip to content

Kubb vs orval, HeyAPI, and openapi-typescript

Kubb, orval, HeyAPI, and openapi-typescript all generate code from OpenAPI specs. The tables below compare their support, feature by feature. Think a row is wrong? Open an issue or PR on kubb-labs/kubb with the evidence.

Plugin and feature coverage

Legend

  • ✅ Built in, no extra config.
  • 🟡 Through a third-party or community plugin.
  • 🔶 Supported, but needs extra user code.
  • 🛑 Not officially supported.
Feature Kubb orval HeyAPI openapi-ts
OpenAPI 2.0, 3.0, 3.1 input
TypeScript types
HTTP client (Axios, Fetch) 🔶2
React Query hooks 🛑
Vue Query composables 🛑
SWR hooks 🛑3 🟡4
Zod validation schemas 1 🛑
MSW request handlers 🛑 🛑
Faker.js mock data 🔶5 🛑 🛑
Cypress E2E tests 🛑 🛑 🛑
MCP server 🛑 🛑 🛑
Redoc API documentation 🛑 🛑 🛑
Barrel index files 🛑 🛑 🛑

Notes

  1. HeyAPI also generates Valibot schemas alongside Zod.
  2. openapi-typescript generates types only. The typed client comes from its openapi-fetch runtime, not per-operation code, and openapi-fetch is now in feature freeze.
  3. HeyAPI lists SWR as a roadmap proposal that has not started.
  4. openapi-typescript relies on the community swr-openapi package.
  5. orval has no standalone Faker output. It fills its MSW handlers with faker data instead.

Type safety and response handling

The first table is about which outputs exist. This one is about how well each tool models a single operation, and how much of that reaches your code.

On coverage the three are close. The gap is ergonomics. Kubb puts the status on the result, so one switch narrows the body and the same call can throw or return its error. orval does this in its fetch client only. HeyAPI gives a status-keyed type map to index, not a value you branch on at runtime.

The legend matches the table above.

Feature Kubb orval HeyAPI
Status-discriminated response result 🔶1 🔶2
Multiple success (2xx) responses
Multiple content types per response 🛑3
default and wildcard (4XX, 5XX) responses 4
Typed error responses 🔶5
Throw or return the error per call 🛑
Zod v4 schemas tied to the types
Recursive schemas
Server-side schema validation

Notes

  1. Only orval's fetch client emits a status-narrowable union. Its axios and query clients return the success type and take the error type on the side.
  2. HeyAPI builds a status-keyed type map you index (Responses[200]), but the SDK returns a flat { data, error } pair with no status to switch on.
  3. On @hey-api/openapi-ts v0.99.0, a response with both application/json and application/xml keeps only the JSON shape. Kubb and orval emit a variant per content type.
  4. orval expands 4XX and 5XX into unions of concrete codes. Kubb and HeyAPI keep the range key.
  5. orval types the error body in its fetch client, or once you wire ErrorType or override.swr.generateErrorTypes. Otherwise it stays Error.

openapi-typescript is omitted here. It ships no generated client, so the runtime rows do not apply.

Client runtime

The generated client does more than wrap fetch. It encodes parameters and bodies from the spec and decodes responses by content type, and it can validate both ends. See serialization for the full picture.

Two things set Kubb's client apart. It reads each parameter's OpenAPI style and explode from the spec, so query, path, header, and cookie all encode correctly with no config. And codecs register a serialize and deserialize per media type, which is how XML or YAML round-trips without replacing the client.

The legend matches the tables above.

Feature Kubb orval HeyAPI
Parameter styles from the spec 🛑1 🔶2
Request body serializers (JSON, form-data, urlencoded) 3
Pluggable codecs per media type (XML, YAML) 🛑4 🛑4
Runtime body validation 5 🔶5 5
Server-sent events and streaming 🛑

Notes

  1. orval interpolates path parameters directly and leaves query encoding to axios or a qs config, with no per-parameter style or explode.
  2. HeyAPI serializes path parameters per parameter but runs one global query serializer, and does not style header or cookie parameters.
  3. All three encode JSON, multipart/form-data, and application/x-www-form-urlencoded. Kubb also honors the OpenAPI encoding object, so a form part can set its own content type and style.
  4. orval and HeyAPI expose a single body serializer and one response transformer, so a new media type means replacing them, not registering one.
  5. Off by default. Kubb validates request and response bodies through any Standard Schema validator (Zod, valibot, arktype). HeyAPI covers those plus Ajv, Joi, TypeBox, and Yup. orval validates responses only, with Zod.

What sets Kubb apart

Plugin architecture

Every output is a separate plugin on a shared AST. Kubb parses the spec once, so names stay consistent across outputs and you add only what you need.

Custom adapters and parsers

A custom adapter swaps adapterOas for another input such as AsyncAPI or GraphQL. A custom parser targets another output language such as Python or Rust. orval and HeyAPI do neither, so Kubb reaches inputs and outputs they cannot.

Post-enforced plugins

Plugins with enforce: 'post' run after the rest, handling cross-output work like barrel files without touching each plugin. @kubb/plugin-barrel works this way.

Bundler integration

unplugin-kubb runs generation inside Vite, Rollup, Webpack, esbuild, Nuxt, and Astro. HeyAPI is Vite-only. orval has no bundler integration.

When not to use Kubb

  • You use only a few endpoints that rarely change.
  • You have no OpenAPI spec and won't write one.
  • You need a non-OpenAPI format now and won't write a custom adapter.