Skip to content
Kit

Plugins

defineExtension with kind plugin defines a Kubb plugin from one object with meta, dependencies, and a setup function that returns the resolved options, plus the KubbPluginSetupContext that registers generators, resolvers, and macros.

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.

defineExtension

defineExtension with kind: 'plugin' takes a definition object and returns a factory. Call it with the user's options to get a Plugin. Kubb runs setup before the plugin's generators, and the value setup returns becomes the plugin's resolved options.

plugin-example.ts
import { defineExtension, type PluginType } from 'kubb/kit'

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

export const pluginExample = defineExtension<PluginExample>({
  kind: 'plugin',
  meta: { name: 'plugin-example', version: '1.0.0' },
  dependencies: { 'plugin-ts': {} },
  setup(options, kubb) {
    // Register resolvers, generators, and macros on `kubb`.
    return { prefix: options.prefix ?? '' }
  },
})

PluginType<TName, TOptions, TResolvedOptions, TResolver> types the plugin. TOptions is what users pass, and TResolvedOptions is what setup returns and generators read. meta and dependencies are shared with parsers and described in Extensions.

Definition shape

PropertyTypeRequiredDescription
kind'plugin'YesTells defineExtension to define a plugin.
metaExtensionMetaYesName, version, and compatibility. meta.name follows plugin-<feature>. The plugin's own name is deprecated in favor of it, and optional when meta.name is set.
setup(options, kubb: KubbPluginSetupContext) => TResolvedOptionsNoMain plugin logic. Receives the user's options and returns the resolved options. Can be async.
dependenciesExtensionDependenciesNoPlugins this one builds on. They run first, and a missing required one fails the run.
enforce'pre' | 'post'NoRun this plugin before ('pre') or after ('post') the normal plugins. Dependency order still wins.
hooks{ 'kubb:plugin:setup'?: ...; ... }NoLifecycle handlers (see Plugin API). kubb:plugin:setup runs after setup.

The Plugin the factory returns keeps the user's raw options in plugin.options, so Studio and config merging can read them back.

Read compatibility from the manifest

A published plugin ships a kubb-extension.json with its compatibility ranges. Import only that field into meta, so Kubb checks the same ranges at runtime and a bundler leaves the rest of the manifest out of the build:

src/plugin.ts
import { defineExtension } from 'kubb/kit'
import { compatibility } from '../kubb-extension.json'
import { version } from '../package.json'

export const pluginExample = defineExtension({
  kind: 'plugin',
  meta: { name: 'plugin-example', version, compatibility },
})

Named imports from JSON need a bundler that inlines them. See Extension manifest.

definePlugin (deprecated)

definePlugin is deprecated. Use defineExtension with kind: 'plugin' instead. The factory form below is removed in the next major.

definePlugin takes the same definition object as defineExtension, without kind. To move over, rename the call and add kind: 'plugin'. See Extensions.

Older plugins pass a function that returns the Plugin directly, with lifecycle handlers in hooks:

plugin-example.ts
import { definePlugin } from 'kubb/kit'

export const pluginExample = definePlugin((options: { prefix?: string } = {}) => ({
  name: 'plugin-example',
  options,
  hooks: {
    'kubb:plugin:setup'(ctx) {
      ctx.setOptions({ prefix: options.prefix ?? '' })
    },
  },
}))

To move this form over, copy the plugin's name into meta.name, move the body of kubb:plugin:setup into setup, return the object you passed to ctx.setOptions, and pass the definition to defineExtension with kind: 'plugin'. While you migrate, the returned plugin can also carry meta, dependencies, and setup, with the same meaning as in the definition object. A plugin without meta gets { name }.

Setup context

setup and kubb:plugin:setup register generators, the resolver, macros, and options through the setup context. A plugin reaches it in one of two ways:

  • The context argument: kubb in setup, and ctx in the kubb:plugin:setup hook.
  • Composables from kubb/kit, such as addGenerator(). They take no context argument, so helper functions that setup calls can use them too.
import { createResolver, defineExtension, defineGenerator } from 'kubb/kit'

const resolver = createResolver({ pluginName: 'plugin-example' })
const generator = defineGenerator({ name: 'example', operation: () => [] })

export const pluginExample = defineExtension({
  kind: 'plugin',
  meta: { name: 'plugin-example' },
  setup(options, kubb) {
    kubb.requirePlugin('plugin-ts')
    kubb.setResolver(resolver)
    kubb.addGenerator(generator)
    return options
  },
})
kubb memberComposableSignaturePurpose
addGeneratoraddGenerator()(...generators: Array<Generator>) => voidRegister one or more generators for this plugin
setResolversetResolver()(resolver: ResolverPatch<Resolver> | Resolver) => voidSet or partially override the file naming resolver
addMacroaddMacro()(macro: Macro) => voidAdd a macro that rewrites AST nodes before generators
setMacrossetMacros()(macros: Array<Macro>) => voidReplace this plugin's macros with a new list
setOptionssetOptions()(options: ResolvedOptions) => voidSet the resolved options used by generators. In setup, return them instead.
injectFileinjectFile()(file: UserFileNode) => voidInject a raw file into the build output, bypassing generation
requirePluginrequirePlugin()(name: string) => PluginReturn another plugin, or fail the run with KUBB_PLUGIN_NOT_FOUND
configuseConfig()ConfigThe resolved build configuration at setup time
optionsuseOptions()TOptionsThe plugin's own options as passed by the user

useKubb() returns the whole setup context. tryUseKubb() does the same, but returns undefined outside setup instead of throwing.

Composables read the context of the plugin whose setup is running, also after an await. Each plugin gets its own context, so composables in one plugin's setup never reach another plugin. Outside setup and kubb:plugin:setup, they throw KUBB_NO_CONTEXT. That includes generators and later hooks, which run after setup and receive their own ctx. Both ways can be used in the same setup.

Plugin names follow the convention plugin-<feature>, such as plugin-react-query or plugin-zod.

See also