OpenAPI/Swagger @kubb/plugin-oas
This guide explains the OpenAPI (formerly known as Swagger) specification and how Kubb uses it to generate code.
What is OpenAPI?
The OpenAPI Specification (OAS) is a standard, language-agnostic interface description for HTTP APIs. It allows both humans and computers to discover and understand the capabilities of a service without access to source code, documentation, or network traffic inspection.
Supported Versions
Kubb supports the following OpenAPI/Swagger versions:
| Version | Status |
|---|---|
| Swagger 2.0 | ✅ Supported |
| OpenAPI 3.0 | ✅ Supported |
| OpenAPI 3.1 | ✅ Supported |
Key Concepts
Paths and Operations
Paths define the endpoints in your API, and operations define the HTTP methods (GET, POST, PUT, DELETE, etc.) available at each path.
paths:
/pets:
get:
operationId: getPets
summary: List all pets
post:
operationId: createPet
summary: Create a petSchemas
Schemas define the structure of request and response bodies using JSON Schema.
components:
schemas:
Pet:
type: object
properties:
id:
type: integer
name:
type: stringTags
Tags are used to group operations together. Kubb uses tags for organizing generated code into folders and files.
tags:
- name: pets
description: Pet operationsHow Kubb Uses OpenAPI
Kubb reads your OpenAPI specification and generates code based on:
- Types: Generated from
components/schemas - API Functions: Generated from
pathsand their operations - Hooks: Generated for data fetching libraries (React Query, SWR, etc.)
- Mocks: Generated from schemas using Faker.js
Parameters with Style and Explode
Kubb correctly handles OpenAPI parameter serialization using the style and explode properties according to the OpenAPI specification.
Form Style with Explode
When a query parameter has style: "form" and explode: true (which is the default for query parameters), object properties are flattened into separate query parameters.
For objects with additionalProperties and no defined properties, Kubb generates a flattened type:
parameters:
- name: customFields
in: query
style: form
explode: true
schema:
type: object
additionalProperties:
type: stringGenerated TypeScript type:
export type QueryParams = {
[key: string]: string
}This allows for dynamic query parameters like ?field1=value1&field2=value2 instead of nesting them under a single parameter name.
Without Explode
When explode: false, the object remains nested:
parameters:
- name: customFields
in: query
style: form
explode: false
schema:
type: object
additionalProperties:
type: stringGenerated TypeScript type:
export type QueryParams = {
customFields?: {
[key: string]: string
}
}Working with OpenAPI Files
Validation
It's recommended to validate your OpenAPI file before using it with Kubb. The @kubb/plugin-oas plugin includes built-in validation.
import { pluginOas } from '@kubb/plugin-oas'
pluginOas({
validate: true, // Enable validation
})Formatting and Filtering
For advanced filtering and sorting of your OpenAPI specification, you can use openapi-format before passing it to Kubb.
See Filter and Sort for more details on filtering operations.
Working with Large OpenAPI Specifications
For large APIs with many endpoints (100+ operations), consider these strategies:
Split by Domain or Microservice
Divide your OpenAPI specification into multiple files by domain:
specs/
├── pets-api.yaml
├── users-api.yaml
└── orders-api.yamlThen use separate Kubb configurations for each:
export default defineConfig({
input: { path: './specs/pets-api.yaml' },
output: { path: './src/gen/pets' },
})Use $ref for Reusability
Reference shared schemas across your specification:
components:
schemas:
Pet:
type: object
properties:
id:
type: integer
name:
type: string
paths:
/pets:
get:
responses:
'200':
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Pet'Organize with Tags
Use tags to logically group operations, which Kubb will use for code organization:
paths:
/pets:
get:
tags: ['pets']
operationId: listPets
/pets/{id}:
get:
tags: ['pets']
operationId: getPetByIdSee Best Practices for how to leverage tags in your Kubb configuration.
Creating OpenAPI Specifications
If you don't have an OpenAPI specification yet, you have several options:
- Manual Creation: Use the Swagger Editor for an interactive editing experience
- Code-First Tools: Generate specs from your existing code:
- tsoa for TypeScript/Node.js
- NestJS OpenAPI for NestJS applications
- FastAPI for Python (auto-generates OpenAPI)
- API-First Tools: Design your API visually:
- Reverse Engineering: Generate from API requests:
Best Practices
Naming Conventions
- Use descriptive operationId: Always define unique, descriptive
operationIdvalues for each operation. Kubb uses these to generate function names.
# ❌ Avoid
operationId: get1
# ✅ Good
operationId: getPetById- Use consistent naming patterns: Follow a consistent pattern across your API for predictable generated code:
getPets,getPetById,createPet,updatePet,deletePetlistUsers,getUser,createUser,updateUser,deleteUser
Schema Design
- Define schemas in components: Place reusable schemas in
components/schemasfor better code reuse and to avoid duplication.
components:
schemas:
Error:
type: object
properties:
code:
type: integer
message:
type: string
Pet:
type: object
required: ['id', 'name']
properties:
id:
type: integer
name:
type: string- Use discriminators for polymorphism: When working with inheritance or union types, use discriminators for better type generation:
components:
schemas:
Pet:
type: object
discriminator:
propertyName: petType
properties:
petType:
type: stringDiscriminators
Kubb provides comprehensive support for OpenAPI discriminators, which are used to handle polymorphism and union types in your API schemas. Discriminators help differentiate between multiple possible schemas in oneOf or anyOf constructs.
Supported Discriminator Patterns
Kubb supports all discriminator patterns from OpenAPI 3.0 and OpenAPI 3.1 specifications:
OpenAPI 3.0 Patterns:
- ✅ With explicit mapping
- ✅ Without mapping (inferred from schema names)
- ✅ With
oneOf - ✅ With
anyOf - ✅ Strict vs Inherit modes
OpenAPI 3.1 Patterns:
- ✅ With
$refinoneOf/anyOf - ✅ Enhanced polymorphism support
Edge Cases:
- ✅ Inline schemas (not just
$ref) - ✅ Extension properties (e.g.,
x-custom-name) - ✅ Const values
- ✅ Single-value enums
- ✅ Mixed
$refand inline schemas - ✅ Title fallback
- ✅ Mixed types in
oneOf(with extension properties)
Discriminator with Explicit Mapping
The most common pattern uses explicit mapping to connect discriminator values to specific schemas:
components:
schemas:
Pet:
type: object
required: [petType]
discriminator:
propertyName: petType
mapping:
cat: '#/components/schemas/Cat'
dog: '#/components/schemas/Dog'
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
Cat:
type: object
properties:
petType:
type: string
enum: [cat]
meow:
type: boolean
Dog:
type: object
properties:
petType:
type: string
enum: [dog]
bark:
type: booleanThis generates proper TypeScript union types with discriminated unions for type narrowing.
Discriminator Without Mapping
When no explicit mapping is provided, Kubb infers the mapping from schema names:
components:
schemas:
Animal:
discriminator:
propertyName: animalType
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'Kubb automatically creates mapping:
{
Cat: '#/components/schemas/Cat',
Dog: '#/components/schemas/Dog'
}Discriminator with Inline Schemas
Kubb supports discriminators with inline schemas (not just $ref):
components:
schemas:
Response:
discriminator:
propertyName: status
oneOf:
- type: object
title: Success
properties:
status:
type: string
const: success
data:
type: object
- type: object
title: Error
properties:
status:
type: string
const: error
message:
type: stringFor inline schemas, Kubb creates synthetic references and uses the const value or title for discriminator mapping.
Discriminator with Extension Properties
When using extension properties as discriminator property names (properties starting with x-):
components:
schemas:
Data:
discriminator:
propertyName: x-custom-type # Extension property
oneOf:
- type: object
x-custom-type: TypeA
properties:
field1:
type: string
- type: object
x-custom-type: TypeB
properties:
field2:
type: numberExtension properties are treated as metadata and don't generate runtime validation constraints, but they still enable proper union type generation.
Discriminator Modes
Kubb supports two modes for discriminator handling:
Strict Mode (default):
import { pluginOas } from '@kubb/plugin-oas'
pluginOas({
discriminator: 'strict', // Default
})In strict mode, the discriminator property is not automatically added to child schemas.
Inherit Mode:
import { pluginOas } from '@kubb/plugin-oas'
pluginOas({
discriminator: 'inherit',
})In inherit mode, Kubb automatically adds the discriminator property with appropriate enum values to each child schema, ensuring type safety.
Best Practices for Discriminators
Use consistent discriminator property names across your API (e.g., always use
typeorkind)Provide explicit mapping when using custom discriminator values
Use const or single-value enum for the discriminator property in child schemas:
Cat:
properties:
petType:
const: cat # Preferred for discriminator valuesEnsure all oneOf/anyOf members share the same base type when possible (all objects or all arrays, not mixed)
Use standard properties (not extensions) when runtime validation is needed
Document the discriminator in your schema description for API consumers
Known Limitations
- Extension property discriminators: When the discriminator property name is an extension (starts with
x-), it's treated as metadata only and doesn't generate runtime validation constraints - Mixed types: While technically supported (e.g., mixing object and array types in oneOf), it's not recommended per OpenAPI spec and may produce unexpected results with validation libraries
For more information about discriminators, see the OpenAPI Specification.
- Array of enums: When defining arrays with enum values, place the
enuminside theitemsschema, not at the array level:
# ❌ Incorrect (malformed schema)
schema:
type: array
enum: ["foo", "bar", "baz"]
items:
type: string
# ✅ Correct
schema:
type: array
items:
type: string
enum: ["foo", "bar", "baz"]INFO
Kubb automatically normalizes malformed array enum schemas (where enum is at the array level) by moving the enum into the items schema. This ensures correct TypeScript types (Type[]) and Zod schemas (z.array(z.enum([...]))) are generated.
Organization
- Use tags consistently: Group related operations with tags for organized code generation. This helps Kubb create logical folder structures.
tags:
- name: pets
description: Pet operations
- name: users
description: User operations- Include descriptions: Add descriptions to schemas, operations, and parameters for better generated documentation and code comments.
paths:
/pets:
get:
summary: List all pets
description: Returns a paginated list of pets with optional filtering
operationId: listPetsVersioning
- Version your API: Use semantic versioning in your API paths or headers:
servers:
- url: https://api.example.com/v1
description: Production API v1- Document breaking changes: Clearly mark deprecated endpoints and provide migration paths:
paths:
/legacy/pets:
get:
deprecated: true
description: Use /pets instead