Skip to content
Fetch logo

Fetch

Officialv6.0.4

@kubb/plugin-fetch

kubb add plugin-fetch

Configure requests

Set the base URL, credentials, shared headers, and interceptors on the generated client, for every call or for one.

Where settings apply

Every generated function sends through the shared client from .kubb/client.ts, unless a call passes its own:

ScopeHowEffect
Every callclient.setConfig({ ... })Merges into the shared config. headers, mediaTypes, and serializer merge per key.
A separate clientcreateClient({ ... })Copies the shared config at creation, without its interceptors.
A child clientclient.extend({ ... })Copies the shared config and runs the parent's interceptors first.
One callThe same key on the callWins over the client for that call.

The client takes baseURL, headers, auth, credentials, options, transport, serializer, mediaTypes, validate, and onValidationError:

import { client, createClient } from './gen/.kubb/client'
import { getPetById } from './gen/clients/getPetById'

client.setConfig({ baseURL: import.meta.env.VITE_API_URL, headers: { 'X-Client': 'web' } })

const tenant = createClient({ baseURL: 'https://tenant.example.com', auth: () => tenantToken })
await getPetById({ path: { petId: 1 }, client: tenant })

await getPetById({ path: { petId: 1 }, baseURL: 'http://localhost:3000' })

Set the base URL

A call uses the first base URL it finds:

OrderSource
1baseURL on the call.
2The operation's own servers, then its path's servers.
3The client passed as client.
4client.setConfig({ baseURL }).
5The plugin's baseURL option.
The spec's top-level servers URL is never used, even with the server option of adapterOas.

See Which server a call picks and Named servers.

Authenticate requests

Set auth to a token, or to a sync or async function that gets each security scheme of the call. It returns the token, or undefined to skip the scheme:

client.setConfig({
  auth: (auth) => (auth.type === 'apiKey' ? apiKey : localStorage.getItem('token') ?? undefined),
})

By default, auth adds no credentials. The scheme decides where the token goes:

Scheme in the specauth getsThe token goes to
http bearer{ type: 'http', scheme: 'bearer' }Authorization: Bearer <token>
http basic{ type: 'http', scheme: 'basic' }Authorization: Basic <base64>
apiKey in header{ type: 'apiKey', name, in: 'header' }the header named name
apiKey in query{ type: 'apiKey', name, in: 'query' }the query parameter name
apiKey in cookie{ type: 'apiKey', name, in: 'cookie' }the Cookie header
oauth2{ type: 'oauth2', scopes, flows }Authorization: Bearer <token>
openIdConnect{ type: 'openIdConnect', scopes, url }Authorization: Bearer <token>
For basic auth, return the raw username:password. The runtime base64-encodes it and adds the Basic prefix.

For a credential that no security scheme describes, such as a request signature, add a request interceptor. See Several security schemes and Mutual TLS.

Add interceptors

client.interceptors has three stacks. Each handler gets the value the one before it returned:

StackRunsGetsReturns
requestAfter the URL, body, and auth are built, before the send.ResolvedRequestThe request.
responseAfter the body is decoded, before the status check and validator.DecodedResponseThe result.
errorOn any failure: ResponseError, network error, abort, ParseError.The errorAnother error to throw, or nothing to keep it.
client.interceptors.request.use((request) => {
  request.headers['X-Request-ID'] = crypto.randomUUID()
  return request
})

ResolvedRequest has url, method, headers, body, signal, credentials, options, and stream. DecodedResponse has data, status, statusText, headers, contentType, request, and response.

Handlers run in the order you register them. use returns an id for eject and update:

const id = client.interceptors.request.use((request) => request)
client.interceptors.request.update(id, (request) => ({ ...request, headers: { ...request.headers, 'X-Trace': 'on' } }))
client.interceptors.request.eject(id)
With throwOnError: false, a non-2xx response resolves, so no error handler runs for it.

Next, use a custom transport.

Less common cases

Which server a call picks

When an operation or path has its own servers, the server.index of adapterOas picks the entry, falling back to the first. A relative URL such as /v2, or one with {variable} placeholders, is skipped, so the call uses the next source in Set the base URL.

The plugin's baseURL option is written into .kubb/config.ts, which .kubb/client.ts passes to setConfig.

Named servers

When an OpenAPI 3.2 server has a name, .kubb/client.ts exports its URL in servers:

servers:
  - url: https://api.example.com
    name: production
  - url: https://staging.example.com
    name: staging

Several security schemes

When an operation lists several security requirements, the runtime sends the first one whose schemes all get a token. A requirement that needs an API key and a session cookie sends both. A header or query key you set yourself wins over the token. An oauth2 scheme also passes metadataUrl to auth when the spec has one.

.kubb/client.ts exports the spec's schemes by name as securitySchemes, and each generated call points at its own, with the scopes it needs:

security: [{ ...securitySchemes.petstore_auth, scopes: ['write:pets'] }]

Mutual TLS

When the spec has a mutualTLS scheme, securitySchemes leaves it out, because its credential is a client certificate on the connection, not on the request. In Node.js, send through an undici Agent that holds the certificate, with a custom transport.

In the browser, the browser picks the client certificate itself.