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

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.

imports.ts
typescript
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.

plugin-example.ts
typescript
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.

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.

my-generator.ts
typescript
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().

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.

resolver.ts
typescript
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

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.

adapterCustom.ts
typescript
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:

kubb.config.ts
typescript
import {  } from 'kubb/config'
import {  } from './adapterCustom.ts'
Cannot find module './adapterCustom.ts' or its corresponding type declarations.
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:

AdapterSource
typescript
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.

adapterStream.ts
typescript
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:

naming.ts
typescript
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.

shell
bun add -d @kubb/adapter-oas@beta
shell
pnpm add -D @kubb/adapter-oas@beta
shell
npm install --save-dev @kubb/adapter-oas@beta
shell
yarn add -D @kubb/adapter-oas@beta

Key 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.
kubb.config.ts
typescript
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:

adapterJsonSchema.ts
typescript
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:

kubb.config.ts
typescript
import {  } from 'kubb/config'
import {  } from './adapterJsonSchema.ts'
Cannot find module './adapterJsonSchema.ts' or its corresponding type declarations.
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:

Conversion pipeline
text
context → [rule.match → rule.convert] → node

The 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

adapterValidated.ts
typescript
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:

parserText.ts
typescript
import {  } from 'kubb/kit'

export const  = ({
  : 'parser-text',
  : ['.txt'],
  () {
    return .
      .(() => . ?? [])
      .(() => (. === 'Text' ? . : ''))
      .('\n')
  },
  (...) {
    return .().('\n')
  },
})

Wire it into your config:

kubb.config.ts
typescript
import {  } from 'kubb/config'
import { ,  } from '@kubb/parser-ts'
import {  } from './parserText.ts'
Cannot find module './parserText.ts' or its corresponding type declarations.
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:

naming.ts
typescript
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.

shell
bun add -d @kubb/parser-ts@beta
shell
pnpm add -D @kubb/parser-ts@beta
shell
npm install --save-dev @kubb/parser-ts@beta
shell
yarn 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.

kubb.config.ts
typescript
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:

parserPython.ts
typescript
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:

kubb.config.ts
typescript
import {  } from 'kubb/config'
import {  } from '@kubb/parser-ts'
import {  } from './parserPython.ts'
Cannot find module './parserPython.ts' or its corresponding type declarations.
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.

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.

renderer.ts
typescript
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.

memory-storage.ts
typescript
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.

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.

ast-namespace.ts
typescript
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.

factory.ts
typescript
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.

factories.ts
typescript
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

walk.ts
typescript
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

transform.ts
typescript
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:

update.ts
typescript
import {  } from 'kubb/kit'

const  = ..({ : 'Pet', : 'object', : [] })

..(, { : 'Pet' }) // -> same `node` reference (no change)
..(, { : 'Animal' }) // -> new node with `name` replaced

collect: gather matching nodes

collect.ts
typescript
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:

guards.ts
typescript
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.
refs.ts
typescript
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

tags.ts
typescript
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.

kubb.config.ts
typescript
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:

kubb.config.ts
typescript
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.

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.

build.ts
typescript
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.

Narrowing config.input

config.input is either the { path: string } form or the { data: string | unknown } form. Narrow between them with an in check:

narrow.ts
typescript
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.

plugin.test.ts
typescript
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