Skip to content
Kit

Extension manifest

Reference for kubb-extension.json, the manifest a Kubb plugin, adapter, or parser ships on npm. Covers every field, the compatibility rule, and the options format Kubb Studio renders as a form.

kubb-extension.json describes an extension package. It sits at the package root and ships on npm. The Kubb registry, kubb add, kubb list, and the Studio agent read it. Generation does not: at runtime Kubb reads the extension's meta.

The JSON Schema is https://kubb.dev/schemas/kubb-extension.json. Set it as $schema to get validation and completion in your editor.

kubb-extension.json
{
  "$schema": "https://kubb.dev/schemas/kubb-extension.json",
  "kind": "plugin",
  "name": "plugin-example",
  "export": "pluginExample",
  "displayName": "Example",
  "description": "Writes a comment with the HTTP method and path of every operation.",
  "category": "documentation",
  "icon": { "light": "https://example.com/icon.svg" },
  "tags": ["comments", "operations"],
  "docs": "https://github.com/acme/kubb-plugin-example#readme",
  "compatibility": { "kubb": ">=5.8.0", "node": ">=22" },
  "options": [
    {
      "key": "prefix",
      "path": ["prefix"],
      "label": "Prefix",
      "description": "Text written at the start of every comment.",
      "type": "text"
    }
  ]
}

Fields

Readers ignore unknown top-level keys.

$schema

string
The JSON Schema URL, https://kubb.dev/schemas/kubb-extension.json. Only editors read it.

kind

'plugin' | 'adapter' | 'parser' required
What the package extends. kubb add adds a plugin to the config and prints the code for an adapter or parser. Studio builds forms only for plugins.

name

string required
The extension's meta.name, such as plugin-zod, oas, or typescript. Kubb skips a manifest whose name is not shaped like an npm package name: lowercase letters, digits, ., _, ~, and -, with an optional @scope/.

export

string required
The factory export users call in kubb.config.ts, such as pluginZod. It must be a JavaScript identifier. kubb add imports this name, and the Studio agent calls it.

displayName

string required
The human-readable name, such as Zod.

description

string required
One sentence on what the extension does.

category

string
Category for filtering, such as validation. The registry replaces it with the main category the reviewer picks from the category list.

icon

{ light: string; dark?: string }
Icon URLs for light and dark backgrounds. Both must use HTTPS. Studio shows a generic icon when the manifest has none.

media

Array<string>
Screenshots or videos, as HTTPS URLs.

tags

Array<string>
Search keywords.

docs

string
Documentation URL, using HTTPS.

compatibility

{ kubb?: string; node?: string }

Semver ranges for Kubb (@kubb/core) and Node.js, such as { "kubb": ">=5.8.0", "node": ">=22" }. Only these two keys are allowed. Any other key makes the manifest invalid, so a typo such as nodejs cannot skip a check.

kubb add and kubb update check the ranges before they install. Kubb checks the same ranges at runtime through meta.compatibility, so import it from the manifest.

role

'client' | 'consumer'
Plugins only. client provides the HTTP client runtime, like @kubb/plugin-fetch. consumer generates code that calls a client but ships none, like @kubb/plugin-react-query. Studio uses it to require a client plugin next to a consumer before it generates.

options

Array<ExtensionManifestOption>
The form fields Studio renders for the extension. See Options.

Options

Each entry in options is one form field. It edits the value at path inside the options object users pass to the factory. Studio reads and writes every field with the same generic rules, so an extension needs no Studio code.

key

string required
A stable UI key, unique within the manifest, such as enumType.

path

Array<string> required
Where the value lives in the options object, such as ["enum", "type"] or ["output", "path"].

label

string required
The field label, such as Enum Type.

description

string
Help text shown under the field.

type

'select' | 'boolean' | 'text' | 'number' | 'list' required
The kind of field. See Field types.

default

JSONValue
The extension's runtime default. Studio shows it when the option is unset and does not write a value equal to it.

choices

Array<{ label?: string; value: JSONValue }>
Required for select. A choice with value: null means "leave unset". A default must equal one of the choice values.

trueValue

JSONValue
For boolean: the value written for true. Defaults to true.

falseValue

JSONValue
For boolean: the value written for false. Defaults to false.

item

JSONValue
For list: the template for each entry. Every "$item" string inside it is replaced by the entry. Without it, entries are plain strings.

itemCase

'upper'
For list: upper-case each entry before writing it.

JSONValue is any value that survives JSON.stringify: a string, number, boolean, null, array, or object.

Field types

'select'

A dropdown of choices. The chosen value is written as is, so it can be an object:

{
  "key": "group",
  "path": ["group"],
  "label": "Group",
  "type": "select",
  "choices": [
    { "label": "none", "value": null },
    { "label": "by tag", "value": { "type": "tag" } },
    { "label": "by path", "value": { "type": "path" } }
  ]
}
'boolean'

A switch. Set trueValue and falseValue when the option is not a plain boolean. This field writes suspense: {} when on and suspense: false when off:

{
  "key": "suspense",
  "path": ["suspense"],
  "label": "Suspense",
  "type": "boolean",
  "default": false,
  "trueValue": {},
  "falseValue": false
}
'text'
A text input. Studio trims the value, and an empty value means unset.
'number'
A number input. A value that is not a number means unset.
'list'

A comma-separated text input that writes an array. With item, each entry fills a template. This field turns pets, store into [{ "type": "tag", "pattern": "pets" }, { "type": "tag", "pattern": "store" }]:

{
  "key": "excludeTags",
  "path": ["exclude"],
  "label": "Exclude Tags",
  "type": "list",
  "item": { "type": "tag", "pattern": "$item" }
}

With "itemCase": "upper", get, post becomes ["GET", "POST"].

How Studio writes a value

  1. text: trims the value. An empty string means unset.
  2. number: converts the value with Number(). An empty value or NaN means unset.
  3. select: writes the chosen value. null means unset.
  4. boolean: writes trueValue (default true) or falseValue (default false).
  5. list: splits on ,, trims each entry, drops empty entries, applies itemCase, and fills item. An empty list means unset.
  6. A value equal to default is not written. ["output", "mode"] is the exception, and Studio always writes it.
  7. Unset removes the key at path from the config. Any other value is set at path.
  8. Writes deep-merge at path, so ["sdk", "mode"] and ["sdk", "name"] share one sdk object.

How Studio reads a value

Studio reads from the JSON copy of the options the agent reports, so a function inside an option never reaches it.

  1. It reads the value at path. A missing value is unset.
  2. select: picks the choice whose value deep-equals it. No match is unset.
  3. boolean: on when the value deep-equals trueValue (default true), or when trueValue is an object and the value is any object. Otherwise off.
  4. list: when the value is an array whose every entry matches item, with $item standing for any string, it joins the matched strings with , . Otherwise unset.
  5. text and number: the value when it is a string, number, or boolean.

A value Studio cannot show stays unset in the form, and the agent keeps the value in kubb.config.ts.

Validation

Kubb and the registry check a manifest at different points:

ReaderChecksOn failure
kubb add, kubb update, kubb list, the Studio agentkind, name, export, displayName, and description are set and well formed, compatibility holds only kubb and node with string ranges, and options is an array.The manifest is skipped.
The registryThe JSON Schema, unique option keys, choices on every select, and a select default that equals one of its choices.The review pull request lists the problem, and the weekly bump keeps the previous pin.

Import compatibility into meta

Keep the ranges in one place. Import only compatibility from the manifest into meta, so a bundler leaves the rest of the file, including options, out of your build:

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

export const pluginExampleName = 'plugin-example'

export const pluginExample = definePlugin({
  meta: { name: pluginExampleName, version, compatibility },
  setup(options) {
    return options
  },
})

Keep name as a typed constant. A JSON import widens it to string. Named imports from JSON need a bundler, such as tsdown, that inlines them. With plain tsc and "module": "NodeNext", write the same ranges in meta.compatibility instead.

See also