Skip to content

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.

Each section below summarizes one layer and links to its full page for more detail.

Pipeline overview

Every run moves through four stages. Select one to see what it does, or watch it play through from spec to files.

You declare the input spec, the output folder, an adapter, and the plugins to run. defineConfig pre-wires the OpenAPI adapter and the default parsers.

input output adapter plugins

Config

defineConfig from kubb/config pre-wires the adapter, the default parsers, and the barrel plugin, so a minimal config only needs input and output. See the defaults table for exactly what each field resolves to.

kubb.config.ts
typescript
import { 
defineConfig
} from 'kubb/config'
export default
defineConfig
({
input
: './petStore.yaml',
output
: {
path
: './src/gen' },
plugins
: [],
})

NOTE

Reach for createKubb only when you need a programmatic build or custom tooling.

Adapter

Input spec OpenAPI 2/3
adapter.parse(source)
InputNode schemas + operations
You hand Kubb an OpenAPI 2 or 3 document.

The adapter reads your spec and returns an InputNode, so nothing downstream touches the original format. It answers every spec-specific question: nullability, $ref resolution, discriminators, and binary detection. @kubb/adapter-oas covers OpenAPI 2.0, 3.0, and 3.1, and defineConfig selects it for you.

AST

InputNode
├─ schemas: SchemaNode[]
└─ operations: OperationNode[]
├─ parameters: ParameterNode[]
├─ requestBody: RequestBodyNode
└─ responses: ResponseNode[]

The AST is the contract between the adapter and the plugins. Every adapter produces one and every plugin reads it, so the same plugin works against any spec. Two visitors walk it: transform to rewrite nodes and collect to gather them.

Macros

InputNode
applyMacros(node, [...])
InputNode transformed
A plugin starts from the shared AST.

Macros are a second AST pass. They rewrite schema and operation nodes, one plugin at a time, before generators print code, so you can rename symbols or retype fields without patching the output. Pass them through a plugin's macros option.

Plugins

AST schemas + operations
FileNodes src/gen

A plugin walks the AST and emits FileNodes. Plugins run in array order, so a types plugin can run first and a client plugin after it imports those types. Browse the plugins catalogue for what ships today.

Generators

SchemaNode / OperationNode or OperationNode[]
gen.schema() / gen.operation() / gen.operations()
FileNode[] or JSX
The engine hands schema/operation/operations from the AST to a generator.

A generator is where a plugin produces code. Each one reads a schema, a single operation, or the whole operation set, and returns the files those nodes become. Splitting a plugin into named generators keeps each one small and lets the engine call the right one for every node.

Renderer

JSX kubb/jsx
jsxRenderer @kubb/renderer-jsx
FileNode
A generator can return JSX and skip building FileNodes by hand.

A generator can build FileNodes by hand or describe them as components, and kubb/jsx is the optional JSX path for the second style.

NOTE

kubb/jsx is optional. Plugins that build FileNodes directly with the factory node builders from kubb/kit do not need it.

Resolvers

Symbol e.g. the Pet type
resolver ctx.getResolver('plugin-ts')
path + name
Several plugins reference the same Pet type, so none of them should hard-code its name or file path.

A resolver answers two questions for every file: its name and its path. Generators ask the resolver instead of building strings, so names stay consistent and one plugin imports another's output by reading its resolver.

Parsers

FileNode[]
parser.parse by file extension
source string
Plugins hand their FileNodes to the file processor.

A parser converts a FileNode into a source string. Each one claims a set of file extensions, and Kubb hands every emitted file to the parser that owns its extension. @kubb/parser-ts and @kubb/parser-md ship by default.

Storage

FileNode[]
storage driver fsStorage / memoryStorage
disk / memory / S3
Every generated file goes to the storage driver, addressed by a root-relative path.

The storage driver decides where files land.

  • fsStorage() writes to disk and skips unchanged files. Default.
  • memoryStorage() keeps everything in a Map, ideal for tests.
  • A custom Storage targets any other backend.

Integrations and AI

  • unplugin-kubb runs Kubb inside your build tool: Vite, Rollup, webpack, esbuild, Rspack, Nuxt, and Astro.
  • The kubb mcp command exposes generation to LLM clients over MCP.