Parsers
A parser turns a FileNode into the source string that storage writes to disk. Each parser registers the file extensions it handles, and the file processor in @kubb/core routes each emitted file to the matching parser.
A parser has two jobs:
print(...nodes) is called by plugins to render language-specific AST nodes into a string before staging them on FileNode.sources. parse(file) is called by the file processor after all plugins have run, to join the staged sources into the final output string.
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.
Quick start
A minimal parser registers its extensions and concatenates each source:
import { } from '@kubb/core'
export const = ({
: 'parser-text',
: ['.txt'],
() {
return .
.(() => . ?? [])
.(() => (. === 'Text' ? . : ''))
.('\n')
},
(...) {
return .().('\n')
},
})Wire it into your config:
import { } from 'kubb'
import { , } from '@kubb/parser-ts'
import { } from './parserText.ts'
export default ({
: { : './petStore.yaml' },
: { : './src/gen' },
: [, , ],
})Anatomy
Every value returned from defineParser matches the Parser interface from @kubb/core:
| 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.
Streaming
The file processor is internal to @kubb/core 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.
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/core'
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'
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
Use defineParser from @kubb/core. It 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/core'
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'
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.