Skip to content

Parsers ​

A parser turns a FileNode into the source string written to disk. This page 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 { 
defineParser
} from 'kubb/kit'
export const
parserText
=
defineParser
(() => ({
name
: 'parser-text',
extNames
: ['.txt'],
parse
(
file
) {
return
file
.
sources
.
flatMap
((
source
) =>
source
.
nodes
?? [])
.
map
((
node
) => (
node
.
kind
=== 'Text' ?
node
.
value
: ''))
.
join
('\n')
},
print
(...
nodes
) {
return
nodes
.
map
(
String
).
join
('\n')
}, }))

Wire it into your config:

kubb.config.ts
typescript

import { defineConfig } from 'kubb/config'
import { parserTs, parserTsx } from '@kubb/parser-ts'
import { parserText } from './parserText.ts'

export default defineConfig({
  input: './petStore.yaml',
  output: { path: './src/gen' },
  parsers: [parserTs(), parserTsx(), parserText()],
})

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) => 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.
copy (file: FileNode, source: string) => UserFileNode No By the file processor, for each copy file Describes a copied template's raw content as nodes, for example its imports as ImportNodes, in the same shape injectFile takes. Kubb builds it with createFile and prints it with parse. Omit it to write copied files verbatim.

IMPORTANT

If two parsers register the same extension, the last one in the parsers array wins. Order matters.

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

A parser is a factory function that returns a Parser object. Call it when you pass it to parsers: in defineConfig:

naming.ts
typescript
import { 
defineParser
} from 'kubb/kit'
export const
parserCustom
=
defineParser
(() => ({
name
: 'custom',
extNames
: ['.custom'],
parse
(
file
) {
return
file
.
sources
.
map
((
source
) =>
source
.
name
?? '').
join
('\n')
},
print
(...
nodes
) {
return
nodes
.
map
(
String
).
join
('\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.

Creating a custom parser ​

defineParser wraps a factory function and infers the parser type, mirroring definePlugin: the factory receives the caller's options, and calling the result without options passes an empty object.

parserPython.ts
typescript
import { 
defineParser
} from 'kubb/kit'
export const
parserPython
=
defineParser
(() => ({
name
: 'parser-python',
extNames
: ['.py', '.pyi'],
parse
(
file
) {
const
lines
:
Array
<string> = []
if (
file
.
banner
) {
lines
.
push
(
file
.
banner
)
} for (const
source
of
file
.
sources
) {
for (const
node
of
source
.
nodes
?? []) {
if (
node
.
kind
=== 'Text') {
lines
.
push
(
node
.
value
)
} } } if (
file
.
footer
) {
lines
.
push
(
file
.
footer
)
} return
lines
.
join
('\n')
},
print
(...
nodes
) {
return
nodes
.
map
(
String
).
join
('\n')
}, }))

Register it alongside the built-ins:

kubb.config.ts
typescript

import { defineConfig } from 'kubb/config'
import { parserTs } from '@kubb/parser-ts'
import { parserPython } from './parserPython.ts'

export default defineConfig({
  input: './petStore.yaml',
  output: { path: './src/gen' },
  parsers: [parserTs(), parserPython()],
})

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.