Axios
Officialv6.0.4@kubb/plugin-axios
Migrate to v6
Upgrade @kubb/plugin-axios from v5 to v6, which runs on the shared client core from @kubb/plugin-client with axios as the transport.
Code that only calls the generated functions, SDK classes, or hooks needs a regenerate. Most other edits land in the file that configures client.
Upgrade steps
Upgrade the packages
Upgrade kubb and @kubb/plugin-axios together. v6 needs Kubb 5.19.0 or later:
npm install --save-dev kubb@latest @kubb/plugin-axios@6Regenerate
This rewrites .kubb/ and the operation files:
npx kubb generateRun the type checker
Most breaking changes show up as compile errors:
npx tsc --noEmitFix each error
Find its row in What to change. Then check the behavior changes, which compile without errors.
What to change
| You used | Now |
|---|---|
@kubb/plugin-axios/templates/axios.ts | @kubb/plugin-axios/templates/client.ts. Types only. |
@kubb/plugin-axios/templates/core.ts, serializers.ts, or standardSchema.ts | @kubb/plugin-client/templates/*. Types only. |
client.interceptors handlers that read axios objects | Move them to getAxiosInstance(), or rewrite them. See Interceptors. |
An axios instance as transport | createAxiosTransport(instance) |
defaultTransport.defaults | getAxiosInstance().defaults. defaultTransport is now a function. |
A transport that returns { data, status, ... } | Return a web Response, or { webResponse, request, response }. Response interceptors get a DecodedResponse, the shape TransportResult had. |
client.interceptors.error handlers that read status | Narrow with ResponseError.is first. They also get network errors (AxiosError), aborts, and validation errors. |
AxiosOptions | NativeOptions, the same type: AxiosRequestConfig. |
validateStatus | Removed. The client splits success from error by status. |
AxiosTransport, InterceptorChannel | Removed. createClientCore, ClientInstance, and Interceptors come from the core. |
InterceptorFn | The core's InterceptorFn<T>, which gets the core objects. |
defaultQuerySerializer | serializeQuery, or serializeStyledQuery for a styled query parameter. |
defaultPathSerializer | serializePath, plus serializeStyledPath for a label or matrix parameter. |
applyHeaderStyles | serializeHeaders |
codecs | mediaTypes. See Media types. |
Codec, Deserializer, ContentBodySerializer, BodySerializer | MediaType |
serializer.body, defaultBodySerializer, isDefaultJsonBody | A mediaTypes entry, or defaultMediaTypes. |
responseType, ResponseType, ResponseTypeMap, resolveResponseType | parseAs and ParseAs, such as parseAs: 'blob', or a media type. |
formMediaTypes | multipartFormData and urlencoded, written to .kubb/mediaTypes/ when the spec has a form body. |
CallResult, isStreamed, getResponseContentType | Removed. Read result.contentType. |
toEventStream, EventStreamCall, EventStreamResult | client.stream and StreamResult. See Streams. |
ServerSentEvent, TypedEvent, or StreamResult from .kubb/sse.ts | Import them from .kubb/client.ts. |
A …Status2XX type, or a '2XX' key in a responses record or validator.error | Lower case: …Status2xx and '2xx'. Upper-case keys are ignored. |
clients/payment/ for tag payment with parent: store | clients/store/payment/. To keep one folder per tag, pass group: { type: 'tag', name: ({ group }) => group }. |
types/Point.ts for operation point next to schema Point | types/PointOperation.ts, whenever a schema file has the same name, in any case. |
new pet(config) with sdk and group: { type: 'tag' } | new PetClient(config). Files and root class fields, such as pet, stay the same. |
clientGenerator from @kubb/plugin-axios | Removed. Build on setupClient and clientDependencies from @kubb/plugin-client. |
Runtime values such as client still come from the generated .kubb/client.ts. The common rewrites:
import type { RequestConfig } from '@kubb/plugin-axios/templates/axios.ts'
import type { ResolvedRequest } from '@kubb/plugin-axios/templates/core.ts'
import type { RequestConfig } from '@kubb/plugin-axios/templates/client.ts'
import type { ResolvedRequest } from '@kubb/plugin-client/templates/core.ts' import { client, defaultTransport } from './gen/.kubb/client'
import { client, createAxiosTransport, getAxiosInstance } from './gen/.kubb/client'
defaultTransport.defaults.timeout = 10_000
client.setConfig({ transport: axios.create({ baseURL }) })
getAxiosInstance().defaults.timeout = 10_000
client.setConfig({ transport: createAxiosTransport(axios.create({ baseURL })) }) transport: async () => ({ data: { id: 1 }, status: 200, statusText: 'OK', headers: new Headers(), request, response })
transport: async () => Response.json({ id: 1 })client.interceptors.error.use((error) => {
if (ResponseError.is(error) && error.status === 401) refreshToken()
return error
}) import type { ListPetsStatus4XX } from './gen/types/ListPets'
import type { ListPetsStatus4xx } from './gen/types/ListPets' import { pet } from './gen/sdk/pet/pet'
const api = new pet({ baseURL })
import { PetClient } from './gen/sdk/pet/pet'
const api = new PetClient({ baseURL })Interceptors
client.interceptors handlers now get the core objects: ResolvedRequest, DecodedResponse, and the error. The quickest fix moves each handler, unchanged, to getAxiosInstance(), the instance v5 called defaultTransport:
client.interceptors.request.use((config) => {
config.headers.set('X-Request-ID', crypto.randomUUID())
return config
})getAxiosInstance().interceptors.request.use((config) => {
config.headers.set('X-Request-ID', crypto.randomUUID())
return config
})client.interceptors.request.use((request) => {
request.headers['X-Request-ID'] = crypto.randomUUID()
return request
})Native response interceptors see the raw bytes, not the decoded body. The core form also works with @kubb/plugin-fetch.
Media types
serialize(body, contentType) becomes encode(body, context), with the type in context.contentType. decode(response, context) gets the Response, not a parsed body, so read the body yourself:
client.setConfig({ codecs: { 'application/xml': { deserialize: (raw) => parseXml(raw as string) } } })
client.setConfig({ mediaTypes: { 'application/xml': { decode: async (response) => parseXml(await response.text()) } } })A key can be a pattern, such as text/*. See Built-in media types.
Streams
client.stream returns the stream directly:
const { stream } = await toEventStream(client({ method: 'GET', url: '/events', responseType: 'stream' }), { shape: 'data' })
const { stream } = await client.stream({ method: 'GET', url: '/events', eventShape: 'data' })StreamResult and UnwrappedStreamResult take the responses record and the full event type, so wrap a payload in ServerSentEvent. A TypedEvent<…> stays as is:
let result: EventStreamResult<PetEvent>
let result: StreamResult<StreamPetsResponses, true, ServerSentEvent<PetEvent>>An operation whose only success type is JSON Lines returns a StreamResult with one parsed item per line, not a string. For stream responses with headers, see Stream result types.
Behavior changes
These compile without errors, so check them by hand:
| Area | v5 | v6 |
|---|---|---|
| Client | client.interceptors.request handlers ran in reverse, as in axios. | They run in registration order. |
| Client | A child from createClient() got its own axios.create(). | It inherits the transport of client. |
| Requests | A path or operation with its own servers used the client's baseURL. | It calls its own server, unless the call sets baseURL. See Set the base URL. |
| Requests | An object query parameter without style: filter[name]=odie. | name=odie&age=3. Set style: deepObject in the spec to keep the brackets. |
| Requests | An array or object header without style went out as JSON. | X-Ids: 3,4 |
| Requests | A space in a query value: +. | %20 |
| Requests | spaceDelimited and pipeDelimited repeated the key. | explode defaults to false, so an array goes out as ids=1%7C2. |
| Requests | A style: cookie parameter was percent-encoded. | Sent as is. A value it cannot send, such as one with ;, throws a TypeError. |
| Responses | Invalid JSON under a JSON media type became a string. | Throws a ParseError. Catch it, or register a lenient application/json entry. |
| Responses | An aborted call or stream rejected with axios's CanceledError. | It rejects with the signal's reason. |
Also new in v6:
- Requests:
body: nullis sent as JSONnull. A stream call sendsAccept: text/event-stream, or its JSON Lines media type. An auth requirement with several schemes sends all of them. - Responses: an empty body is
undefinedfor every media type, text included. A failed call without a body has{ status, statusText }aserror. Every result and thrownResponseErrorhas the responseHeadersasheaders. - Validation:
ResponseError.dataholds the validated body. - Types: a
2xxkey is a success, a range key such as4xxtypesstatusas the codes it covers, and an exact code such as200wins over a range. XML, YAML, and CSV responses arestring, so JSON and XML together givedata: Pet | string. Image, audio, video, PDF, andapplication/octet-streamresponses areBlob, even withtype: string.result.contentTypeis the bare media type, such as'text/html'. - Serializers: each has its own file in
.kubb/serializers/, andsetConfig({ serializer })merges per key. AcreateClientCoreclient gets no serializers, so pass them in itsserializerconfig. - Servers:
server.indexno longer counts a server without aurl.
If you upgrade from 5.12 or earlier, also check these changes.
Query plugins
@kubb/plugin-react-query, @kubb/plugin-vue-query, @kubb/plugin-swr, and @kubb/plugin-mcp work with v5 and v6, so you can upgrade them separately. They skip hooks for JSON Lines and event stream operations.
Less common cases
New in v6
These v6 features need no code change:
- Validation:
validator.errortakes a map by status, such as{ 404: getPetStatus404Schema }, to check an error body against the schema of its status. - Serializers:
serializer.headerandserializer.cookieare new. See Override parameter serialization. - Servers: named servers are exported as
serversfrom.kubb/client.ts. See Named servers.
From 5.12 or earlier
These changed in 5.13 and compile without errors:
- A stream call keeps the adapter set on the call or instance, such as a test mock. Only a browser call with the default adapters switches to the fetch adapter.
- A successful stream call with a non-stream body, such as JSON, throws a
TypeErrorwith the body as itscause. A successful answer with no body still ends as an empty stream. - An event that does not end with a blank line is dropped, as in browser
EventSource, so end every event with one.
SDK root class
With an SDK root class, add interceptors on api.client, because the root class shares one client with every tag. See sdk.
Handle errors
Catch a ResponseError, return errors as values with throwOnError: false, and tell a 4xx or 5xx answer apart from a request that never completed.
Configure serialization
Change how the generated client encodes parameters by their OpenAPI style and explode, and how it encodes and decodes bodies by media type.
