Skip to content
D
Documentation

Instances and defaults

concept
3 min readUpdated

Ky instances collect request defaults so you can share a transport policy while keeping different API clients independent. Use ky directly for one-off requests, ky.create() for a new set of defaults, and ky.extend() when a client should inherit and modify another client.

How the defaults compose

create() starts a new instance. It does not inherit defaults from the default instance or from another client. extend() starts from its parent, then merges the options you provide. A function form of extend() receives the parent options, so a child can derive a value such as a longer URL prefix.

mermaid
flowchart LR
    A["ky.create(defaults)"] --> B["base client"]
    B --> C["ky.extend(changes)"]
    C --> D["child client"]
    E["request options"] --> F["merged request"]
    B --> F
    D --> F

The instance exposes method shortcuts such as get() and post(). Each shortcut returns a response promise with body methods such as .text() and .json(); the method shortcut supplies the HTTP method while the instance supplies its defaults.

Create a shared client

Pass an Options object to create(). The following client gives every relative request a base URL, a path prefix, a header, and a request hook. The trailing slash on baseUrl keeps page-relative paths under /api/ when standard URL resolution applies.

ts
import ky from 'ky';

async function main(): Promise<void> {
  const api = ky.create({
    baseUrl: 'https://api.example.com/api/',
    prefix: 'v1',
    headers: {
      accept: 'application/json',
    },
    hooks: {
      beforeRequest: [({request}) => {
        request.headers.set('x-client', 'web');
      }],
    },
    retry: 2,
    timeout: 10_000,
  });

  const response = await api.get('users/42');
  console.log(response.url);
}

main();

The request uses the instance's defaults and resolves to a URL under the configured base URL and prefix. The hook runs immediately before the request is sent, and retry and timeout apply to requests made through this instance unless a request supplies different values.

Extend a client for a narrower API

Call extend() on the parent instance when a child shares the parent's policy. The function form lets you read the parent options. This example adds users to the existing prefix and adds a child-specific header without rebuilding the base client.

ts
import ky from 'ky';

async function main(): Promise<void> {
  const api = ky.create({
    baseUrl: 'https://api.example.com/api/',
    prefix: 'v1',
    headers: {
      accept: 'application/json',
    },
  });

  const usersApi = api.extend(parentOptions => ({
    prefix: `${parentOptions.prefix}/users`,
    headers: {
      'x-resource': 'users',
    },
  }));

  const response = await usersApi.get('42');
  console.log(response.url);
}

main();

usersApi keeps the base URL and inherited accept header, adds the resource header, and sends GET /api/v1/users/42. Calling api.get('version') still uses the parent prefix and does not use the child prefix.

Know what is merged

By default, extend() deep-merges options. Headers merge, search parameters accumulate, and hook arrays append. This is useful for layering policy, but it is not a replacement operation.

OptionTypeDefaultEffect when extended
baseUrlstring | URL—Resolves relative input after prefix is applied. An absolute input bypasses it.
prefixstring | URL—Joins before URL resolution; a slash at the join boundary is normalized.
headersheader object—Merges with parent headers.
hooksHooks—Appends hook arrays by default.
searchParamsSearchParamsOption''Accumulates with the parent's search parameters.
retryRetryOptions | number—Supplies retry policy for requests from the instance.
timeoutnumber | false—Sets the per-attempt response timeout in milliseconds.

Use replaceOption when a child must replace a merged value rather than add to it. For example, this child runs only its own beforeRequest hook:

ts
import ky, {replaceOption} from 'ky';

async function main(): Promise<void> {
  const api = ky.create({
    hooks: {
      beforeRequest: [() => {
        console.log('parent hook');
      }],
    },
  });

  const child = api.extend({
    hooks: replaceOption({
      beforeRequest: [() => {
        console.log('child hook');
      }],
    }),
  });

  await child.get('https://example.com');
}

main();

The child hook replaces the parent's beforeRequest array. The same marker can replace other deep-merged values, including headers, search parameters, context, and signals.

Choose baseUrl or prefix

Use baseUrl in most cases. It follows standard URL resolution: an input beginning with / starts at the origin root, so it can override a path in the base URL. Use prefix when an origin-relative input such as /users must be treated as page-relative and appended to the prefix. The prefix is joined before baseUrl resolves the resulting input.

For a base URL with a path, include its trailing / when you want users and ./users to extend that path rather than replace its last segment. A Request input bypasses both baseUrl and prefix; searchParams still applies.

Was this page helpful?

Instances and defaults — ky · GPT-5.6 Luna