How-to guides

Interceptors

Run code on every call the Kubb client makes. Add or rewrite headers before the send, observe or transform the response, react to errors, and register, replace, or remove handlers at runtime.

Register handlers on client.interceptors.request, .response, or .error to apply behavior across calls. Fetch handlers receive resolved request/result objects. Axios handlers receive native Axios objects.

Run before the send

Request handlers run after URL construction, serialization, and authentication. Return the modified request:

import { client } from './gen/.kubb/client'

client.interceptors.request.use((request) => {
  request.headers['X-Request-ID'] = crypto.randomUUID()
  return request
})

On fetch the handler receives a ResolvedRequest (url, method, headers, body, signal, credentials, options, responseType). On axios it receives an InternalAxiosRequestConfig, so headers go through AxiosHeaders and the body is on data.

Run after the send

Response handlers run before calls resolve. Fetch handlers run before the status split and deserialization, so data is the raw body.

client.interceptors.response.use((result) => {
  console.info(`${result.status} ${result.request.url}`)
  return result
})

The fetch handler receives a TransportResult (data, status, statusText, headers, contentType, request, response), the axios handler an AxiosResponse.

React to an error

Use an error handler to react to HTTP errors on the throwing path:

client.interceptors.error.use((error) => {
  if (error.status === 401) scheduleTokenRefresh()
  return error
})

The fetch handler receives the ResponseError, the axios handler an AxiosError, and this channel only fires on the throw path. When you read with throwOnError: false, a documented non-2xx response resolves and no error interceptor runs, so inspect the returned error on the result instead. A transport failure (no response at all) still throws on both, but only axios fires the error channel for it, since the channel is wired directly into its own rejection handling, while fetch has no result to hand the interceptor stacks. See error handling for that path.

Add, replace, and remove handlers

use registers a handler and returns an id. Pass that id to eject to remove it, or to update to swap its function in place. Response and error handlers run in the order they were registered, and so do request handlers on plugin-fetch, but plugin-axios delegates to axios, which runs them in reverse registration order.

const id = client.interceptors.request.use((request) => request)

client.interceptors.request.update(id, (request) => {
  request.headers['X-Trace'] = 'on'
  return request
})

client.interceptors.request.eject(id)

Where to set them

Register handlers on the shared client for all operations, or on a separate createClient instance for isolated calls. Interceptors are instance-level settings.

See also