Kit API
kubb/kit contains everything you need to build your own plugin and custom logic. Plugins, generators, resolvers, parsers, adapters, renderers, and storage backends all start here. It is a subpath of the top-level kubb package, so there is nothing extra to install. Import straight from kubb/kit.
import {
,
,
,
,
,
,
,
,
,
,
,
} from 'kubb/kit'kubb/kit is backed by the @kubb/kit package, which re-exports from the internal @kubb/core and @kubb/ast libraries. Treat both as implementation details and always import from kubb/kit, never from the internal packages directly.
TIP
The build-time engine (createKubb) and defineConfig come from the kubb package and its kubb/config subpath, documented in Engine and configuration below. kubb/kit is the authoring side: the code you write to add a new plugin, generator, resolver, parser, adapter, or renderer.
Plugin authoring
Plugins are the main extension point in Kubb. A plugin owns its file naming, its output folder, its lifecycle hooks, and the generators that walk the AST and emit FileNode objects.
definePlugin
definePlugin wraps a factory function and returns a typed Plugin. All lifecycle handlers live under one hooks object, inspired by Astro integrations.
import { } from 'kubb/kit'
export const = ((: { ?: string } = {}) => ({
: 'plugin-example',
: {
'kubb:plugin:setup'() {
// Register resolvers, generators, and options here.
},
},
}))Plugin shape
| Property | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique plugin identifier (e.g., plugin-ts) |
dependencies | Array<string> | No | Names of other plugins this one requires |
options | unknown | No | User-supplied options passed through to generators |
hooks | { 'kubb:plugin:setup'?: ...; ... } | Yes | Lifecycle handlers (see Plugin API) |
KubbPluginSetupContext methods (passed to kubb:plugin:setup)
| Method | Signature | Purpose |
|---|---|---|
addGenerator | (...generators: Array<Generator>) => void | Register one or more generators for this plugin |
setResolver | (resolver: Partial<Resolver>) => void | Set or partially override the file naming resolver |
addMacro | (macro: Macro) => void | Add a macro that rewrites AST nodes before generators |
setMacros | (macros: Array<Macro>) => void | Replace this plugin's macros with a new list |
setOptions | (options: ResolvedOptions) => void | Set the resolved options used by generators |
injectFile | (file: UserFileNode) => void | Inject a raw file into the build output, bypassing generation |
updateConfig | (config: Partial<Config>) => void | Merge a partial config update into the current build config |
config | Config | The resolved build configuration at setup time |
options | TOptions | The plugin's own options as passed by the user |
IMPORTANT
Plugin names should follow the convention plugin-<feature> (e.g., plugin-react-query, plugin-zod). See Creating plugins for naming conventions.
Related
defineGenerator
defineGenerator declares a named generator unit consumed by a plugin. Generators walk the AST and emit files. The engine calls each method for the matching node type during the generation loop.
Each generator method returns TElement | Array<FileNode> | void. Returning a renderer element (for example JSX from kubb/jsx) requires a renderer factory on the generator. Returning Array<FileNode> directly, or calling ctx.upsertFile() and returning void, works without a renderer.
import { , } from 'kubb/kit'
const = ({
: 'my-generator',
(, ) {
return [
..({
: `${.}.ts`,
: `./${.}.ts`,
: [
..({
: [..(`export const op = '${.}'`)],
}),
],
}),
]
},
})Generator methods
| Method | Input | Output | When to use |
|---|---|---|---|
schema() | SchemaNode (per data schema) | TElement | Array<FileNode> | void | Generate types, validators, factories. Called once per schema |
operation() | OperationNode (per API operation) | TElement | Array<FileNode> | void | Generate hooks, clients, handlers. Called once per operation |
operations() | Array<OperationNode> (all operations) | TElement | Array<FileNode> | void | Generate index or barrel files. Called once after all operations |
GeneratorContext properties (the ctx argument passed to each method)
| Property | Type | Purpose |
|---|---|---|
ctx.config | Config | Resolved Kubb configuration |
ctx.root | string | Absolute path to the output directory for the current plugin |
ctx.options | TResolvedOptions | Per-node resolved options (after exclude/include/override filtering) |
ctx.plugin | Plugin | The owning plugin descriptor |
ctx.resolver | Resolver | Resolver for the current plugin |
ctx.driver | KubbDriver | Plugin driver for cross-plugin access |
ctx.hooks | AsyncEventEmitter<KubbHooks> | Event bus for KubbHooks events |
ctx.adapter | Adapter | The adapter that parsed the input spec |
ctx.meta | InputMeta | Document metadata from the adapter. Carries title, version, baseURL, and the pre-computed circularNames and enumNames arrays. |
ctx.addFile() | (...files: FileNode[]) => Promise<void> | Add files, skipping any that already exist |
ctx.upsertFile() | (...files: FileNode[]) => Promise<void> | Add or merge files (concatenates sources and imports) |
ctx.getPlugin() | (name: string) => Plugin | undefined | Get a plugin by name |
ctx.requirePlugin() | (name: string) => Plugin | Get a plugin by name or throw a descriptive error |
ctx.getResolver() | (name: string) => Resolver | Get a resolver by plugin name |
ctx.info() | (message: string) => void | Emit an info message via the build event system |
ctx.warn() | (message: string) => void | Emit a warning via the build event system |
ctx.error() | (error: string | Error) => void | Emit an error via the build event system |
TIP
Return an empty array [] to skip a node without error. Return void to handle file writing manually via ctx.upsertFile().
Related
- AST concepts for node types and traversal
- Creating plugins
defineResolver
defineResolver creates a resolver that controls file naming and path resolution for a plugin. The builder is a zero-argument function. Use this to call sibling resolver methods.
The builder must return at least { name, pluginName }. The other resolver methods (default, resolveOptions, resolvePath, resolveFile, resolveBanner, resolveFooter) receive built-in defaults and can each be overridden.
import { } from 'kubb/kit'
import type { , } from 'kubb/kit'
// Extend the base Resolver with plugin-specific naming methods.
type = & {
(: { : string }): string
}
type = <'plugin-example', object, object, >
export const = <>(() => ({
: 'default',
: 'plugin-example',
() {
return this.(., 'function')
},
}))Auto-injected resolver defaults
| Method | Default behavior |
|---|---|
default | camelCase for function/file, PascalCase for type |
resolveOptions | Applies exclude, include, and override filters |
resolvePath | Resolves to output.path, with optional tag/path-based subdirectories |
resolveFile | Constructs a full FileNode using default + resolvePath |
resolveBanner | Returns output.banner or the standard "Generated by Kubb" header |
resolveFooter | Returns output.footer when set |
Related
Adapters
An adapter converts an input specification into the universal AST that every plugin reads. This section documents createAdapter, the Adapter interface, the built-in OpenAPI adapter, and how to build your own. For what adapters are and where they sit in the pipeline, see Adapters concepts.
TIP
For OpenAPI 2.0, 3.0, and 3.1 use the official @kubb/adapter-oas. Kubb picks it for you when you import defineConfig from the kubb package. Write a custom adapter only when you target a different specification such as AsyncAPI, GraphQL, JSON Schema, or gRPC.
createAdapter
createAdapter builds adapters that translate specs into Kubb's universal AST. Write a custom adapter when your source is something Kubb does not parse yet, such as a GraphQL schema, a gRPC definition, an AsyncAPI spec, or your own domain-specific language.
A minimal adapter declares a name and returns an empty InputNode. An empty AST emits nothing, so fill schemas and operations from your spec next.
import { , } from 'kubb/kit'
import type { } from 'kubb/kit'
type = <'adapter-custom', { ?: boolean }, { : boolean }>
export const = <>(() => ({
: 'adapter-custom',
: { : ?. ?? false },
: null,
async () {
return ..({ : [], : [] })
},
() {
return []
},
async () {
// Throw or call ctx.error here when the spec is invalid.
},
}))Wire it into your config with defineConfig from kubb and pass the adapter:
import { } from 'kubb/config'
import { } from './adapterCustom.ts'
export default ({
: { : './my-spec.json' },
: { : './src/gen' },
: ({ : true }),
: [],
})Adapter anatomy
Every adapter returned from createAdapter matches the Adapter interface from kubb/kit:
| Property | Type | Required | Purpose |
|---|---|---|---|
name | string | Yes | Unique adapter identifier. Convention is adapter-<id>. |
options | TResolvedOptions | Yes | Adapter options after defaults are applied. |
document | TDocument | null | Yes | The raw parsed source document, for plugins that need direct access. null before parse(). |
parse | (source: AdapterSource) => InputNode | Promise<InputNode> | Yes | Convert the spec into the universal AST. The build driver consumes the returned InputNode directly. |
getImports | (node: SchemaNode, resolve: (name: string) => { name: string; path: string }) => Array<ImportNode> | Yes | Track cross-references so plugins emit correct imports. resolve receives the collision-corrected schema name and returns the { name, path } for the import. |
validate | (input: string, options?: { throwOnError?: boolean }) => Promise<void> | Yes | Validate the document at a path or URL without running the full pipeline. |
stream | (source: AdapterSource) => Promise<InputNode<true>> | No | Streaming variant of parse(). Returns schemas and operations as AsyncIterables. The OAS adapter uses this path for every spec. |
AdapterSource takes one of two shapes. Handle every form your users may pass:
type = { : 'path'; : string } | { : 'data'; : string | unknown }IMPORTANT
Throw from parse() with a clear, user-facing message when the input is invalid. Kubb surfaces the error verbatim.
Streaming adapters
stream() returns an InputNode<true> whose schemas and operations are AsyncIterables instead of arrays. Each for await loop runs a fresh parse pass over the cached document, so plugins iterate independently and the runtime never holds every node in memory at once.
The build driver prefers stream() when an adapter implements it. For parse()-only adapters, the driver wraps the result in a reusable AsyncIterable so the rest of the pipeline stays stream-shaped.
import { , } from 'kubb/kit'
import type { } from 'kubb/kit'
type = <'adapter-stream', <string, never>>
async function* (): <.> {
// yield each parsed schema as soon as it is ready
}
async function* (): <.> {
// yield each parsed operation as soon as it is ready
}
export const = <>(() => ({
: 'adapter-stream',
: {},
: null,
async () {
throw new ('Use stream() instead. adapter-stream does not support eager parsing.')
},
async () {
return ..({
: true,
: (),
: (),
: { : 'Streamed spec', : [], : [] },
})
},
() {
return []
},
async () {
// Throw or call ctx.error here when the spec is invalid.
},
}))Build the result with ast.factory.createInput({ stream: true, schemas, operations, meta }) (see AST). The meta field is optional. Set it when you can, so plugins read title, version, and baseURL before the first node is yielded.
Adapter naming convention
Adapters share the layout of plugins, so getResolver, the registry, and the docs find them by inference:
| Surface | Pattern | Example |
|---|---|---|
| npm package | @<scope>/adapter-<name> or kubb-adapter-<name> | @kubb/adapter-oas |
| Adapter runtime name | The spec identifier (lowercase) | 'oas' |
| Factory export | adapter<Name> (camelCase) | adapterOas |
| Name constant | adapter<Name>Name | adapterOasName |
AdapterFactoryOptions alias | Adapter<Name> (PascalCase) | AdapterOas |
Export the runtime name as a satisfies-typed constant so consumers reference it without typos:
import { , } from 'kubb/kit'
import type { } from 'kubb/kit'
export type = <'example', { ?: boolean }, { : boolean }, unknown>
export const = 'example' satisfies ['name']
export const = <>(() => ({
: ,
: { : . ?? false },
: null,
async () {
return ..()
},
() {
return []
},
async () {
// Throw or call ctx.error here when the spec is invalid.
},
}))Built-in adapters
@kubb/adapter-oas
Official adapter for OpenAPI 2.0 (Swagger), OpenAPI 3.0, and OpenAPI 3.1. Every official plugin is built against it. See the @kubb/adapter-oas reference for the full option list.
bun add -d @kubb/adapter-oas@betapnpm add -D @kubb/adapter-oas@betanpm install --save-dev @kubb/adapter-oas@betayarn add -D @kubb/adapter-oas@betaKey options:
| Option | Type | Default | Purpose |
|---|---|---|---|
validate | boolean | true | Run OpenAPI schema validation before parsing. |
dateType | false | 'string' | 'stringOffset' | 'stringLocal' | 'date' | 'string' | How format: date/date-time schemas are emitted in TypeScript. |
server | { index?: number; variables?: Record<string, string> } | none | Which servers[] entry to use as the base URL, and its variable overrides. |
import { } from 'kubb/config'
import { } from '@kubb/adapter-oas'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
: ({ : true, : 'date', : { : 0 } }),
})NOTE
defineConfig from the kubb package uses adapterOas() when you omit adapter. Set adapter: only to configure adapterOas options or supply a different adapter.
Creating a custom adapter
Use createAdapter with AdapterFactoryOptions to model your input format. This JSON Schema adapter exposes the parsed document for plugins:
import { , } from 'kubb/kit'
import type { } from 'kubb/kit'
type = { : string; ?: <string, unknown> }
type = <'adapter-json-schema', { ?: boolean }, { : boolean }, >
export const = <>(() => {
let : | null = null
return {
: 'adapter-json-schema',
: { : ?. ?? false },
get () {
return
},
async (): <.> {
if (. !== 'path') {
throw new ('adapter-json-schema requires { type: "path" } input')
}
= { : 'https://json-schema.org/draft/2020-12/schema', : {} }
return ..({
: .(. ?? {}).(() => ..({ , : 'object', : [] })),
: [],
})
},
(, ) {
if (. === 'ref' && .) {
const = (.)
return [..({ : [.], : . })]
}
return []
},
async () {
// Throw or call ctx.error here when the spec is invalid.
},
}
})Register the adapter in kubb.config.ts:
import { } from 'kubb/config'
import { } from './adapterJsonSchema.ts'
export default ({
: { : './schema.json' },
: { : './src/gen' },
: ({ : true }),
: [],
})Schema dispatch and dialects
Turning a spec's schema objects into SchemaNodes is the heaviest part of an adapter. Most of that work is generic JSON Schema (oneOf/anyOf/allOf, enum, const, type, format, items, properties), so adapters follow one contract:
context → [rule.match → rule.convert] → nodeThe adapter derives a small context from each schema, then runs it through an ordered table of dispatch rules that map spec shapes onto AST nodes. Only a few decisions differ between specs. Those live behind a dialect, a single object the converter pipeline reads, so it never hard-codes OpenAPI assumptions:
| Decision | OpenAPI | AsyncAPI (example) |
|---|---|---|
| nullable | nullable: true, x-nullable, or type: ['…','null'] | type: ['…', 'null'] |
| discriminator | a structured discriminator object (not the Swagger 2 string form) | no discriminator object |
| binary | contentMediaType: 'application/octet-stream' | contentEncoding: 'binary' |
| optionality | a parent's required plus the schema's nullable set optional / nullish | same JSON Schema required + null |
@kubb/adapter-oas ships the OpenAPI dialect as its default. A new adapter such as @kubb/adapter-asyncapi reuses the same converters and dispatch table and supplies only its own dialect, so the spec-specific surface stays small. You test it by swapping that one object.
Validate before parsing
import { , } from 'kubb/kit'
import type { } from 'kubb/kit'
type = <'adapter-validated', <string, never>>
export const = <>(() => ({
: 'adapter-validated',
: {},
: null,
async () {
if (. !== 'path' || !..('.yaml')) {
throw new ('Expected a .yaml input file')
}
return ..({ : [], : [] })
},
() {
return []
},
async () {
if (!.('.yaml')) {
throw new ('Expected a .yaml input file')
}
},
}))Parsers
A parser turns a FileNode into the source string written to disk. This section documents defineParser, the Parser interface, the built-in parsers, and how to add your own. For why parsers exist and where they sit in the pipeline, see Parsers concepts.
TIP
For TypeScript and JavaScript output use the built-in @kubb/parser-ts. It is added by default when you import defineConfig from the kubb package. Build a custom parser only when you target a different language, such as Python, Kotlin, or Rust.
defineParser
defineParser creates a parser that converts generated file ASTs to formatted source strings. Each parser declares which file extensions it handles via extNames. A minimal parser registers its extensions and concatenates each source:
import { } from 'kubb/kit'
export const = ({
: 'parser-text',
: ['.txt'],
() {
return .
.(() => . ?? [])
.(() => (. === 'Text' ? . : ''))
.('\n')
},
(...) {
return .().('\n')
},
})Wire it into your config:
import { } from 'kubb/config'
import { , } from '@kubb/parser-ts'
import { } from './parserText.ts'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
: [, , ],
})Parser anatomy
Every value returned from defineParser matches the Parser interface from kubb/kit:
| Property | Type | Required | When called | Purpose |
|---|---|---|---|---|
name | string | Yes | Unique parser identifier. Convention is parser-<id>. | |
extNames | Array<FileNode['extname']> | undefined | Yes | File extensions this parser handles. Set to undefined to register a catch-all fallback. | |
parse | (file: FileNode, options?: { extname?: FileNode['extname'] }) => string | Yes | By the file processor after all plugins run | Serializes the file's staged sources into the final output string. Must return synchronously. |
print | (...nodes: TNode[]) => string | Yes | By plugins, before files are staged | Renders compiler AST nodes to source text. The node type is parser-specific, for example ts.Node for parserTs. |
IMPORTANT
If two parsers register the same extension, the first one in the parsers array wins. Order matters.
NOTE
parse() is synchronous. The file processor streams files through a synchronous pipeline, so returning a Promise is not supported. Do async work before the file reaches the parser and pass the result through FileNode.
NOTE
Formatting and linting (Prettier, Biome, oxlint) run after parse(). Keep parse() focused on producing syntactically valid output.
When no parser matches a file's extension, the file processor joins the file's source strings directly.
Parser naming convention
Parsers share the layout of plugins and adapters:
| Surface | Pattern | Example |
|---|---|---|
| npm package | @<scope>/parser-<name> or kubb-parser-<name> | @kubb/parser-ts |
| Parser runtime name | The output language or format (lowercase) | 'typescript', 'markdown' |
| Factory export | parser<Name> (camelCase) | parserTs, parserMd |
Parsers export a plain Parser object, not a factory function. Pass them directly to parsers: in defineConfig:
import { } from 'kubb/kit'
export const = ({
: 'custom',
: ['.custom'],
() {
return ..(() => . ?? '').('\n')
},
(...) {
return .().('\n')
},
})TIP
Parsers compose by extension. parserTs (.ts, .js) and parserTsx (.tsx, .jsx) ship in the same @kubb/parser-ts package and register side by side.
Built-in parsers
@kubb/parser-ts
The default parser for TypeScript and JavaScript output. It uses the official TypeScript compiler to resolve import paths, deduplicate declarations, print JSDoc, and rewrite extensions based on output.extension. See the @kubb/parser-ts reference for the full option list.
bun add -d @kubb/parser-ts@betapnpm add -D @kubb/parser-ts@betanpm install --save-dev @kubb/parser-ts@betayarn add -D @kubb/parser-ts@beta| Export | Extensions handled | Notes |
|---|---|---|
parserTs | .ts, .js | TypeScript and plain JavaScript output. |
parserTsx | .tsx, .jsx | Same as parserTs with JSX support. |
Both expose parse(file, options?) and print(...nodes: ts.Node[]). Call parserTs.print(node) from a plugin to render a TypeScript compiler node to its source string before staging it on FileNode.sources.
import { } from 'kubb/config'
import { , } from '@kubb/parser-ts'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
: [, ],
})TIP
defineConfig from the kubb package installs parserTs, parserTsx, and parserMd automatically. Set parsers: only when you add a custom parser or need to change the registration order.
Creating a custom parser
defineParser is an identity wrapper that infers the parser type. It returns the object you pass in unchanged, with no per-build options:
import { } from 'kubb/kit'
export const = ({
: 'parser-python',
: ['.py', '.pyi'],
() {
const : <string> = []
if (.) {
.(.)
}
for (const of .) {
for (const of . ?? []) {
if (. === 'Text') {
.(.)
}
}
}
if (.) {
.(.)
}
return .('\n')
},
(...) {
return .().('\n')
},
})Register it alongside the built-ins:
import { } from 'kubb/config'
import { } from '@kubb/parser-ts'
import { } from './parserPython.ts'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
: [, ],
})TIP
Set extNames: undefined to register a catch-all fallback that runs when no other parser matches. Useful for a default .txt writer or for inspecting what files the build produces.
NOTE
parse() runs synchronously, so external formatting (a service call, a child process, or a worker thread) must finish before the file reaches the parser. Stage the pre-formatted output on FileNode.sources[].nodes inside a generator, then let the parser join it verbatim.
How the file processor runs parsers
The file processor is internal to the Kubb engine and processes files one at a time. The build driver enqueues each file as plugins emit it, the processor runs it through parse(), and the result lands in storage without buffering the full set. Progress surfaces as start, update (with { file, source, processed, total, percentage }), and end events on the main event bus, which the built-in reporters render. Memory stays flat regardless of build size because each file is pulled through the pipeline one at a time.
Rendering
createRenderer
createRenderer takes a builder function and returns a factory that produces a Renderer, the object a generator's renderer field points at. It follows the same builder-to-factory shape as createStorage: call the builder once, get back a reusable factory, call the factory to get an instance.
Reach for createRenderer when a generator needs to emit something other than plain FileNode arrays or kubb/jsx elements, for example a renderer that walks a different templating format into FileNodes. kubb/jsx's own jsxRenderer ships as a plain factory and does not depend on createRenderer, so most plugin authors only need createRenderer when they are building an alternative to JSX rendering.
Related
jsxRenderer, the shipped JSX renderer- Creating plugins
jsxRenderer (via kubb/jsx)
For JSX-based rendering, import jsxRenderer from kubb/jsx, backed by the internal @kubb/renderer-jsx package.
jsxRenderer is a React-free recursive renderer. It walks the JSX components into FileNodes without a React reconciliation pass, and its stream() returns a synchronous Generator<FileNode> that skips a microtask per file. Components run as plain functions, so hooks and suspense are not available.
import { } from 'kubb/jsx'
const = ()Set the renderer on a generator through its renderer field (renderer: jsxRenderer) to enable JSX-based output for that generator. Leave it unset, or pass renderer: null, to opt out of rendering. See the JSX API reference for File, Function, Type, Const, and the jsx-runtime / jsx-dev-runtime subpaths.
Storage
Storage backends decide where generated files are written. Kubb ships a filesystem backend and an in-memory one. Use createStorage to build your own.
createStorage
createStorage takes a builder function (options: TOptions) => Storage and returns a factory (options?: TOptions) => Storage. Call the returned factory to instantiate the storage, optionally with options.
import { } from 'kubb/kit'
export const = (() => {
const = new <string, string>()
return {
: 'memory',
async () {
return .()
},
async () {
return .() ?? null
},
async (, ) {
.(, )
},
async () {
.()
},
async () {
const = [....()]
return ? .(() => .()) :
},
async () {
if (!) return .()
for (const of .()) if (.()) .()
},
async () {
.()
},
}
})TIP
Use memoryStorage for tests and dry runs. Use fsStorage for normal development and CI/CD.
Storage interface
The Storage interface is the shape every backend implements. A Storage instance is what the engine consumes at build time and returns from driver.storage.
| Method | Params | Returns | Purpose |
|---|---|---|---|
hasItem() | key: string | Promise<boolean> | Check whether an item exists |
getItem() | key: string | Promise<string | null> | Retrieve an item's content |
setItem() | key: string, value: string | Promise<void> | Write an item |
removeItem() | key: string | Promise<void> | Delete an item |
getKeys() | base?: string | Promise<string[]> | List keys, optionally filtered by prefix |
clear() | base?: string | Promise<void> | Delete all items, optionally scoped by prefix |
dispose?() | (none) | Promise<void> | Optional teardown hook called after the build completes |
fsStorage
fsStorage is the built-in filesystem storage backend. Kubb uses it by default when no storage option is set in the config. It creates output directories automatically and respects output.path.
memoryStorage
memoryStorage is the built-in in-memory storage backend. It writes nothing to disk, so it suits plugin tests, CI validation, and dry runs.
NOTE
Both fsStorage and memoryStorage are exported from kubb/kit and can be passed directly to the storage field at the root of your config.
Related
AST and node builders
ast
ast is kubb/kit's namespace for the entire AST surface, the same way TypeScript groups its node constructors under ts.factory. It carries the factory node builders, the walk, transform, and collect visitors, the guards, the ref and string helpers, and the macro engine. The namespace is backed by the internal @kubb/ast library. Always reach it through kubb/kit.
import { } from 'kubb/kit'
const = ..({
: [..({ : 'Pet', : 'object', : [] })],
: [],
})Node building goes through ast.factory. ast.factory.createFile, ast.factory.createSource, and ast.factory.createText build the FileNode tree a generator returns.
import { } from 'kubb/kit'
const = ..({
: 'pet.ts',
: './pet.ts',
: [..({ : [..('export type Pet = { id: number }')] })],
})For why the AST exists and how it fits the pipeline, see AST concepts.
Schema node types
A SchemaNode is discriminated by its type. The values fall into three families.
Structural types
| Type | Description | TypeScript |
|---|---|---|
object | Object with named properties | { name: string; age: number } |
array | Sequence of items | string[] |
tuple | Fixed-length array with typed positions | [string, number, boolean] |
union | One of multiple types | string | number |
intersection | Combination of multiple types | A & B |
enum | Fixed set of literal values | 'active' | 'inactive' |
Scalar types
| Type | Description | TypeScript |
|---|---|---|
string | Text value | string |
number | Numeric value | number |
integer | Whole number | number |
bigint | Large integer | bigint |
boolean | True/false | boolean |
null | Null value | null |
any | Any value | any |
unknown | Unknown value | unknown |
void | No value | void |
never | Never produced | never |
Special types
| Type | Description | Example |
|---|---|---|
ref | Reference to another schema | Pet (from $ref) |
date | ISO date | 2024-01-15 |
datetime | ISO datetime | 2024-01-15T10:30:00Z |
time | ISO time | 10:30:00 |
uuid | UUID string | 550e8400-e29b-41d4-a716-446655440000 |
email | Email address | [email protected] |
url | URL string | https://example.com |
blob | Binary data | Raw bytes |
Factory functions
Factories return defaulted, fully typed nodes. Use them in adapters and inside generator handlers. Never build AST literals by hand.
import { } from 'kubb/kit'
const = ..({
: [..({ : 'Pet', : 'object', : [] }), ..({ : 'Status', : 'enum', : ['active', 'inactive'] })],
: [..({ : 'listPets', : 'GET', : '/pets' })],
})The ast.factory namespace also provides constructors for source files and TypeScript-level artifacts that generators emit:
| Factory | Purpose |
|---|---|
createFile, createSource, createText | Build FileNodes emitted by generators. |
createImport, createExport | Emit import / export statements. |
createConst, createFunction, createArrowFunction, createJsx | Emit TypeScript declarations and JSX. |
createParameter | Describe operation parameters. |
createProperty, createType | Compose object properties and TypeScript types. |
createResponse, createRequestBody, createContent, createOutput | Model responses, request bodies, content entries, and generator outputs. |
createBreak | Emit line breaks between nodes. |
update | Apply an identity-preserving shallow update to any node. |
Visitors
Three visitor functions cover the common traversal patterns. Visitor objects use lowercase, kind-style keys (input, operation, schema, property, parameter, response). To rewrite nodes inside a plugin, reach for macros. They add names, ordering, and composition on top of transform.
walk: async traversal with side effects
import { } from 'kubb/kit'
const = ..({ : [], : [] })
await .(, {
async () {
.(`Found ${.} ${.}`)
},
async () {
if ('deprecated' in && .) {
.(`Schema ${'name' in ? . : '?'} is deprecated`)
}
},
})Use walk to log, validate, collect statistics, or trigger a side effect per node.
transform: synchronous, returns a new tree
import { } from 'kubb/kit'
const = ..({ : [], : [] })
const = .(, {
() {
if (. === 'object' && . === ) {
return { ..., : false }
}
return
},
() {
return { ..., : .?. ? . : ['untagged'] }
},
})Use transform to change AST structure, normalize inconsistencies, or annotate nodes.
NOTE
transform preserves identity through structural sharing. When a visitor leaves a node and all its descendants unchanged, transform returns the original reference, so unchanged subtrees and their arrays are reused, not copied. Returning the same node is a no-op. Returning a new node replaces it and rebuilds only its ancestors. A no-op pass allocates nothing, and you detect whether anything changed with result === input.
To apply a change and keep that guarantee, use the update factory instead of spreading by hand. It returns the same node when every field you pass already matches:
import { } from 'kubb/kit'
const = ..({ : 'Pet', : 'object', : [] })
..(, { : 'Pet' }) // -> same `node` reference (no change)
..(, { : 'Animal' }) // -> new node with `name` replacedcollect: gather matching nodes
import { } from 'kubb/kit'
const = ..({ : [], : [] })
const = .<.>(, {
() {
return . === 'POST' ? :
},
})
const = .<.>(, {
() {
return 'deprecated' in && . ? :
},
})
.(`POST operations: ${.}`)
.(`Deprecated schemas: ${.}`)Use collect to find specific nodes, filter by a criterion, or build a list for later processing.
Guards and narrowing
Kubb exports type guards and a narrowSchema helper for safe discrimination:
import { } from 'kubb/kit'
const = ..({ : [], : [] })
await .(, {
async () {
const = .(, 'object')
if () {
.(`object with ${..} properties`)
}
if (. === 'ref') {
.(`reference to: ${.}`)
}
},
async () {
if (.()) {
.(`${.} ${.}`)
}
},
})Refs and naming helpers
The ref and naming helpers ship on the ast namespace, alongside the other string and code-building utilities. Reach them the same way you reach the guards or node types.
| Helper | Purpose |
|---|---|
extractRefName | Turn '#/components/schemas/Pet' into 'Pet'. |
childName | Derive a child property name from context. |
enumPropName | Convert an enum value into a valid property name. |
import { } from 'kubb/kit'
const = .('#/components/schemas/Pet')
Constants
| Export | Purpose |
|---|---|
schemaTypes | Map of every schema type discriminant. |
Macros
A macro is a named, composable transform built on transform. Macros rewrite nodes before printing, with ordering, gating, and reuse that a bare visitor does not give you. See Macros concepts.
| Export | Purpose |
|---|---|
defineMacro | Type a macro and read it as one definition. |
composeMacros | Fold an ordered list of macros into one visitor. |
applyMacros | Run a list of macros over a node tree. |
Printers
Lower-level helpers for parsers that turn the AST into source code:
| Export | Purpose |
|---|---|
createPrinter | Typed helper for creating a Printer. |
createPrinter takes an overrides map to replace the handler for individual schema node types. Inside an override, this.base(node) runs the built-in handler the override replaced, so you can wrap its output instead of re-implementing it. Pass overrides through the overrides field rather than spreading them into nodes, otherwise this.base cannot find the original handler.
See Parsers concepts for how parsers consume printers. defineDialect is the adapter seam for spec-specific schema behavior. It keeps the shared converters generic, so an adapter supplies only the questions that differ between specs. See Schema dispatch and dialects.
Collect every operation tag
import { } from 'kubb/kit'
const = ..({ : [], : [] })
const = new (
.<string>(, {
() {
return .?.[0]
},
}),
)
.([...])Diagnostics
Diagnostics
Diagnostics is the namespace a plugin or adapter uses to build and narrow the structured errors Kubb collects during a build. Throw or return a Diagnostics.Error instead of a bare Error when you want a stable code, a severity, and a location attached.
| Member | Purpose |
|---|---|
Diagnostics.Error | Constructs a diagnostic-carrying error with a code, severity, and message |
Diagnostics.hasError | Narrows an array of diagnostics to whether any has severity: 'error' |
Diagnostics.isProblem | Guards a diagnostic down to the problem kind (as opposed to performance or update) |
See the Diagnostics reference for the full list of stable codes Kubb ships with, and how Diagnostics.hasError and Diagnostics.isProblem are used together after a build.
Engine and configuration
The engine that runs your plugins comes from the kubb package and its kubb/config subpath, backed by the internal @kubb/core library. This section documents that surface: defineConfig, createKubb, and the build types they share.
defineConfig
defineConfig adds TypeScript type-checking to a kubb.config.ts file. It comes from the kubb package and fills in defaults for any field you omit.
import { } from 'kubb/config'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
})It accepts a config object, an array of configs, a Promise, or a function. The function form receives the CLI options at runtime, so you can toggle behavior on flags like --watch:
import { } from 'kubb/config'
export default (({ }) => ({
: { : './petStore.yaml' },
: { : './src/gen', : ! },
}))Defaults applied for omitted fields
| Field | Default |
|---|---|
root | process.cwd() |
adapter | adapterOas() |
parsers | [parserTs, parserTsx, parserMd] |
reporters | [cli, json, file] |
plugins | pluginBarrel() appended when not already present |
output.barrel | { type: 'named' }, only when pluginBarrel is in plugins |
output.format | false |
output.lint | false |
IMPORTANT
defineConfig comes from the kubb package. Import it from kubb or the kubb/config subpath.
TIP
The output.barrel default of { type: 'named' } applies only when pluginBarrel is present in plugins. A plugins list without pluginBarrel leaves barrel generation untouched.
Related
createKubb
createKubb drives Kubb from your own code. It accepts a UserConfig and returns a Kubb instance. Calling .build() runs the full generation pipeline and returns a BuildOutput.
Reach for createKubb when you orchestrate several builds, inspect diagnostics, or feed Kubb output into a larger toolchain. For a one-off build, chain the call: await createKubb(config).build().
Import createKubb from the kubb package. Unlike defineConfig, createKubb adds no defaults, so pass adapter, parsers, and your plugins yourself.
createKubb takes a plain config object, the same shape defineConfig produces in kubb.config.ts. It is not a fluent builder. The config stays plain serializable data so Kubb can validate it against the shipped JSON schema.
import { } from 'kubb'
import { } from 'kubb/kit'
import { } from '@kubb/adapter-oas'
import { , } from '@kubb/parser-ts'
import { } from '@kubb/plugin-ts'
import { } from '@kubb/plugin-axios'
const = ({
: (),
: [, ],
: { : './petStore.yaml' },
: { : './gen' },
: [(), ()],
})
const { , , , } = await .()
if (.()) {
for (const of .(.)) {
if (. === 'error') {
.(`${. ?? 'kubb'}: ${.}`)
}
}
.(1)
}
// Per-plugin timings are carried as `performance` diagnostics.
for (const { , } of .(.)) {
.(`${}: ${}ms`)
}
.(`Generated ${.} files`)
const = await .()
.(() => .(` ${}`))Kubb instance members (all getters are read-only)
| Member | Type | Description |
|---|---|---|
.setup() | () => Promise<void> | Initializes the driver and storage. build() calls this automatically. |
.build() | () => Promise<BuildOutput> | Runs the full pipeline and throws a BuildError when any diagnostic is an error. |
.safeBuild() | () => Promise<BuildOutput> | The canonical call. Runs the full pipeline and collects problems in BuildOutput.diagnostics instead of throwing. |
.hooks | AsyncEventEmitter<KubbHooks> | Read-only. Shared event emitter. Attach listeners before calling build(). |
.config | Config | Read-only. Resolved config, available right after createKubb since it resolves in the constructor. |
.storage | Storage | Read-only getter. Final source code keyed by absolute path. Available after setup(), throws before. |
.driver | read-only getter | Advanced plugin driver handle, available after setup(). Throws if accessed before setup(). |
BuildOutput fields
| Field | Type | Description |
|---|---|---|
files | Array<FileNode> | Generated files with paths, names, and content |
storage | Storage | Generated source code accessible via the Storage API |
driver | driver handle | Advanced plugin driver handle for introspection |
diagnostics | Array<Diagnostic> | Problems collected during the build, plus a performance diagnostic per plugin |
Each Diagnostic carries a code, a severity (error, warning, or info), a message, and the plugin that produced it. Failed-plugin diagnostics keep the original error on cause. A performance diagnostic (kind: 'performance') carries a duration in milliseconds. Use the Diagnostics.isProblem, Diagnostics.isPerformance, and Diagnostics.isUpdate guards to narrow by kind.
WARNING
After safeBuild(), check Diagnostics.hasError(diagnostics) before processing files. Plugins can fail without safeBuild() throwing. build() throws a BuildError in that case.
Related
Narrowing config.input
config.input is either the { path: string } form or the { data: string | unknown } form. Narrow between them with an in check:
import type { } from 'kubb/kit'
declare const : <['input']>
if ('path' in ) {
const = . // narrowed to string
} else {
const = . // narrowed to the spec object or string
}Testing
kubb/kit/testing is a separate subpath for the Vitest-backed helpers used to test plugins, generators, and adapters. It stays separate from kubb/kit so importing the authoring toolkit never pulls Vitest into a plugin's runtime dependencies.
import { , , } from 'kubb/kit/testing'createMockedPlugin and createMockedAdapter build a minimal plugin or adapter for a test without wiring a full config. createMockedPluginDriver builds a driver around a set of plugins so a generator can run through its real lifecycle in isolation. renderGeneratorSchema, renderGeneratorOperation, and renderGeneratorOperations call a generator's schema(), operation(), or operations() method directly against a node fixture and return the resulting files. matchFiles asserts a set of generated FileNodes matches expected paths and contents, the assertion most generator tests end on.
See also
- Kit concepts for why the authoring toolkit is a separate surface from the engine
- JSX API reference for
kubb/jsx, the JSX renderer - Plugin concepts for lifecycle hooks, generators, resolvers, and the plugin registry
- AST concepts for
InputNode,OperationNode,SchemaNode, and traversal - Adapter concepts on how adapters convert specs to the universal AST
- Parser concepts on converting
FileNodeAST to source strings - Macros concepts for
defineMacro,composeMacros, andapplyMacros - Barrel files for barrel generation with
@kubb/plugin-barrel - Creating plugins for a step-by-step guide to building a full plugin
- Programmatic usage recipes with
createKubbusage patterns - Configuration reference for all
defineConfigoptions