Skip to content
Integrations

Import an OpenAPI spec in TypeScript with openapi-import

Import a .openapi.yaml spec in TypeScript with openapi-import and get the types and enums your kubb.config.ts generates, without generated files.

openapi-import lets you import a spec like a TypeScript module and get the output of every plugin in your kubb.config.ts, without generated files:

import { type Pet, petStatusEnum } from './petStore.openapi.yaml'

It is a TypeScript content mapper, so tsc and your editor type-check the plugin output in place of the spec. Go to definition, hover, and rename jump between the generated types and the keys in the spec.

openapi-import is experimental. It needs TypeScript 7.1.0-beta or later with --runExternalCode.

Install openapi-import

Install it with Kubb and the TypeScript beta:

pnpm add -D kubb openapi-import typescript@beta

Register the extension

Add the content mapper to tsconfig.json, and name the spec <name>.openapi.yaml:

tsconfig.json
{
  "compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "noEmit": true
  },
  "contentMappers": [{ "package": "openapi-import", "extensions": [".openapi.yaml", ".openapi.yml"] }],
  "include": ["./src/**/*"]
}

Run the type check

pnpm tsc --noEmit --runExternalCode

With npm, a bare tsc runs TypeScript 6, so call the binary from typescript.

VS Code only passes --runExternalCode in trusted workspaces.

Choose the plugins

Add a kubb.config.ts to set plugin options or to run other plugins, and install each plugin it imports:

Terminal
pnpm add -D @kubb/plugin-ts
kubb.config.ts
import { pluginTs } from '@kubb/plugin-ts'
import { defineConfig } from 'kubb/config'

export default defineConfig({
  output: { path: './src/gen' },
  plugins: [pluginTs()],
})

Add ./kubb.config.ts to include in tsconfig.json. The mapper finds the config like the CLI does and reloads it when it changes. With several configs, a spec uses the one whose input matches it.

Load the spec at run time

TypeScript does not emit the spec to JavaScript, so values such as petStatusEnum need a bundler. Add kubb() from any build tool integration without a config option, so it reads the same kubb.config.ts as tsc:

vite.config.ts
import kubb from 'kubb/vite'
import { defineConfig } from 'vite'

export default defineConfig({
  plugins: [kubb()],
})

The types and the values then come from the same import:

src/pets.ts
import { type GetPetByIdPath, type GetPetByIdResponse, petStatusEnum } from '../petStore.openapi.yaml'

export async function getAvailablePet({ petId }: GetPetByIdPath): Promise<GetPetByIdResponse | undefined> {
  const response = await fetch(`https://petstore3.swagger.io/api/v3/pet/${petId}`)
  const pet: GetPetByIdResponse = await response.json()

  return pet.status === petStatusEnum.available ? pet : undefined
}

A config with an input also writes the generated files at build time. Leave input out to import specs only. The bundler reads kubb.config.ts once, so restart it after you change the config.

Limits

  • Code a plugin writes to a separate file, such as a bundled client runtime, is not included. Point imports like the client's importPath at a package.
  • Files that kubb.config.ts imports are not watched. Restart the TypeScript server after you change them.
  • A changed spec runs a full Kubb build: about 0.4 s for a 10 KB spec and 2.6 s for a 1.4 MB spec.

See also