Skip to content

TypeScript

Kubb is built in TypeScript end-to-end. Every public surface accepts a generic that pins down options, resolved options, and the resolver shape. This includes defineConfig, definePlugin, defineMiddleware, defineParser, createAdapter, defineGenerator, and the AST factories. The result is a config file where IntelliSense leads you through every choice and the compiler catches mistakes before generation runs.

Quick start

Use the kubb.config.ts entry point and the compiler will infer everything from there:

kubb.config.ts
typescript
import {  } from 'kubb'
import {  } from '@kubb/plugin-ts'

export default ({
  : { : './petStore.yaml' },
  : { : './src/gen', : true },
  : [
    ({
      : { : 'models' },
      // Hover any option to see the inferred type.
    }),
  ],
})

Strict mode

Kubb assumes TypeScript strict mode. All exported types are written to compile cleanly under "strict": true, and several APIs (notably the AST node guards and resolvers) only narrow correctly when strictNullChecks is on:

tsconfig.json
json
{
  "compilerOptions": {
    "strict": true,
    "moduleResolution": "bundler",
    "module": "ESNext",
    "target": "ES2022"
  }
}

IMPORTANT

If you cannot enable full strict, enable at least strictNullChecks. Without it, RefSchemaNode.ref and resolver helpers return widened types and you'll have to cast manually.

Typing plugin options

Plugins use a single generic, PluginFactoryOptions, that carries four pieces of information through the entire plugin lifecycle:

Generic Purpose
TName The plugin's name literal (e.g. 'plugin-ts'). Used by dependencies lookups.
TOptions The user-facing options accepted by the factory.
TResolvedOptions The shape of options after defaults have been applied (what runs at runtime).
TResolver The plugin's Resolver extension (pluginName, naming helpers).

Declare a PluginFactoryOptions alias once and reuse it:

plugin-example.ts
typescript
import {  } from '@kubb/core'
import type { ,  } from '@kubb/core'

type  = { ?: string }
type  = <>
type  = <'plugin-example', , , >

export const  = <>(() => {
  const :  = { : . ?? '.ts' }

  return {
    : 'plugin-example',
    : ,
    : {
      'kubb:plugin:end'({  }) {
        // `files` is FileNode[]; no cast required.
        .(`${.} files emitted with suffix ${.}`)
      },
    },
  }
})

TIP

Inside hooks, ctx.options is typed as TResolvedOptions and ctx.config is the fully-resolved Config. No casts required.

Typing adapter options

Adapters follow the same pattern with AdapterFactoryOptions. Pin TName, TOptions, TResolvedOptions, and the parsed TDocument once:

adapter-example.ts
typescript
import {  } from '@kubb/core'
import {  } from '@kubb/ast'
import type {  } from '@kubb/core'

type  = { ?: boolean }
type  = <>
type  = { : <string, unknown> }
type  = <'adapter-example', , , >

export const  = <>(() => ({
  : 'adapter-example',
  : { : . ?? false },
  : null,
  async () {
    return ()
  },
  () {
    return []
  },
  async () {
    // Throw or call ctx.error here when the spec is invalid.
  },
}))

The same alias flows into Adapter<AdapterExample>, so consumers that import the adapter type get full type information about its options and document.

Typing middlewares and parsers

Middlewares accept their own options generic on defineMiddleware:

middleware-stats.ts
typescript
import {  } from '@kubb/core'

type  = { ?: string }

export const  = <>(() => ({
  : 'middleware-stats',
  : {
    'kubb:plugins:end'({  }) {
      .(`${. ?? 'kubb'}: ${.} files`)
    },
  },
}))

Parsers receive a FileNode<TMeta> in parse, so typing the parameter keeps plugin-attached metadata typed:

parser-typed.ts
typescript
import {  } from '@kubb/core'
import type {  } from '@kubb/ast'

type  = { : 'ts' | 'tsx' }

export const  = ({
  : 'parser-typed',
  : ['.ts'],
  (: <>) {
    const  = . // typed as Meta
    return `// ${?. ?? 'unknown'}\n`
  },
  () {
    return ''
  },
})

Narrowing AST nodes

The SchemaNode union shares one kind: 'Schema' discriminator and uses node.type to differentiate variants. Use the type guards from @kubb/ast to narrow without casts:

narrow.ts
typescript
import { ,  } from '@kubb/ast'
import type {  } from '@kubb/ast'

declare const : 

const  = (, 'ref')
if (?.) {
  const : string = .
  .()
}

const : unknown = 
if (()) {
  // child is now SchemaNode
  const : 'Schema' = .
}

Available guards on @kubb/ast: isInputNode, isOutputNode, isOperationNode, isHttpOperationNode, isSchemaNode. For schema variants use narrowSchema(node, type); for HTTP operations use isHttpOperationNode(node) to narrow to an HttpOperationNode with non-nullable method/path.

See also

  • Plugins: definePlugin, PluginFactoryOptions, resolvers, generators.
  • Adapters: createAdapter and AdapterFactoryOptions.
  • AST: node types, visitors, guards.
  • Configuration: top-level defineConfig shape.