Architecture
Kubb turns API specifications into code through a layered pipeline. The adapter parses the spec into a universal AST. Macros rewrite AST nodes before a plugin reads them. Plugins walk the AST and emit FileNodes. Parsers convert each FileNode into source code. Storage writes the result to disk.
Pipeline overview
Config
defineConfig from the kubb/config package pre-wires adapterOas, the default parsers parserTs, parserTsx, parserMd, and pluginBarrel. A minimal config only needs input and output.
import { } from 'kubb/config'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
: [],
})NOTE
Reach for createKubb from the kubb package only when you need a programmatic build or custom tooling.
Adapter
An adapter converts an input specification into the universal AST. adapter.parse(source) returns an InputNode, and adapter.getImports(node, resolve) tracks cross-references so plugins emit correct import paths.
Each adapter carries a dialect, and that dialect is the one place where spec-specific schema questions live: nullability, $ref resolution, discriminators, binary detection, and schema deduplication. Everything past the adapter is generic JSON Schema, so plugins and parsers never branch on the source format.
The official adapter for OpenAPI 2.0, 3.0, and 3.1 is @kubb/adapter-oas. defineConfig selects it automatically.
import { } from 'kubb/config'
import { } from '@kubb/adapter-oas'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
: ({ : true, : 'date' }),
})See Adapters for the full list of options and details on building a custom adapter.
AST
The AST is the intermediate representation between the adapter and the plugins. Every adapter produces an InputNode and every plugin consumes it. Plugins never read the raw spec, so the same plugin works with any adapter.
InputNode
├── schemas: SchemaNode[] (named, reusable schemas)
│ └── consumed by plugins → FileNode (e.g. type aliases, enums)
└── operations: OperationNode[]
├── parameters: ParameterNode[] → SchemaNode
├── requestBody?: RequestBodyNode → content: ContentNode[] → SchemaNode
├── responses: ResponseNode[] → content: ContentNode[] → SchemaNode
└── consumed by plugins → FileNode (e.g. client functions, hooks)The AST layer ships three visitor patterns:
| Visitor | Purpose |
|---|---|
walk(root, visitors) | Async traversal for logging, validation, and side effects. |
transform(root, visitors) | Produces a modified copy of the tree. Return null to remove a node. |
collect(root, visitors) | Gathers matching nodes into a flat array. |
Macros
Macros are the second layer of the AST. They are named, composable transforms that rewrite schema and operation nodes before a plugin's generators print code. Use them to rename symbols, retype fields, or normalize shapes without forking an adapter or a generator. Because they run on the shared AST, the same macro works across every adapter and output target.
Macros run per plugin, so one plugin's macros never change the nodes another plugin sees. Pass them through a plugin's macros option, or register them from kubb:plugin:setup with addMacro.
import { } from 'kubb/config'
import { } from '@kubb/plugin-ts'
import { } from 'kubb/kit'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
: [({ : [.] })],
})See Macros for writing macros, composing them, and the built-in presets.
Plugins
Plugins walk the AST and emit FileNodes. They run in array order, so earlier plugins produce types that later plugins can import.
import { } from 'kubb/config'
import { } from '@kubb/plugin-ts'
import { } from '@kubb/plugin-axios'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
: [(), ()],
})See the plugins catalogue for the full list.
Renderer
Plugins can use kubb/jsx, backed by @kubb/renderer-jsx, to describe generated files as React components instead of constructing FileNodes by hand.
NOTE
kubb/jsx is optional. Plugins that build FileNodes directly with the factory node builders from kubb/kit do not need it.
Parsers
A parser converts a FileNode into a source string. Each parser declares which file extensions it handles, and Kubb dispatches every emitted file to the first matching parser.
import { } from 'kubb/config'
import { , } from '@kubb/parser-ts'
import { } from '@kubb/parser-md'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
: [, , ],
})IMPORTANT
When two parsers claim the same extension, the first one wins.
| Package | Extensions | Description |
|---|---|---|
@kubb/parser-ts | .ts, .js, .tsx, .jsx | Uses the TypeScript compiler to print, deduplicate, and resolve imports. Included automatically with kubb. |
@kubb/parser-md | .md, .markdown | Writes Markdown files. Included automatically with kubb. |
Storage
The storage driver controls where Kubb writes generated files. The default is fsStorage(). Use memoryStorage() for testing, or implement Storage to target any backend.
import { } from 'kubb/config'
import { } from 'kubb/kit'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
: (),
})| Driver | Description |
|---|---|
fsStorage() | Writes to disk. Skips unchanged files. Default. |
memoryStorage() | Stores output in a Map. Nothing touches disk. Ideal for tests. |
| Custom | Implement Storage with createStorage to write to S3, a database, or any backend. |
Integrations
| Package | Description |
|---|---|
unplugin-kubb | Runs Kubb during your build via unplugin. Works with Vite, Rollup, webpack, esbuild, Rspack, Nuxt, and Astro. |
See the Integrations page for setup instructions for each build tool.
AI
| Package | Purpose |
|---|---|
@kubb/mcp | Standalone MCP server that lets LLM clients trigger generation directly. |