# Share clients and URL defaults

Use a reusable [`ky`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky) instance when several requests share an API origin, path prefix, headers, or request options, then override them per request or replace them for a derived client.

## When to use it

Create one client for a service boundary, then use that client for requests to the service. `ky.create()` gives the instance a new set of defaults. `ky.extend()` creates a child instance that inherits the parent's defaults and changes selected values.

## Create a client

Install Ky first:

```bash
npm install ky
```

The exported [`KyInstance`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky) type describes the reusable client. Create the client at a module boundary that your request code can import:

```ts title="api.ts"
import ky, {type KyInstance} from 'ky';

export const api: KyInstance = ky.create({
	baseUrl: 'https://api.example.com/',
	prefix: 'v1/',
	headers: {
		accept: 'application/json',
		'x-client': 'dashboard',
	},
	retry: 2,
	timeout: 10_000,
});
```

The client resolves a relative input such as `users` against the base URL after applying the prefix, so this example targets the service's `v1/users` path. The default headers and retry and timeout settings apply to requests made through `api`.

## Override defaults for one request

Pass request options to the instance method. A request-level `baseUrl`, `prefix`, header, or retry value takes precedence for that request; wrap `headers` with `replaceOption` when that request must replace the shared headers:

```ts title="users.ts"
import {replaceOption} from 'ky';
import {api} from './api.js';

interface User {
	id: number;
	name: string;
}

async function loadUsers(): Promise<void> {
	const users = await api.get<User[]>('users', {
		headers: replaceOption({
			accept: 'application/json',
			'x-request-id': 'dashboard-users',
		}),
		retry: 0,
	}).json();

	const healthApi = api.extend({
		headers: replaceOption({
			'x-client': 'health-check',
		}),
	});

	const health = await healthApi.get('health', {
		baseUrl: 'https://status.example.com/',
		prefix: '',
	}).text();

console.log(users, health);
}

void loadUsers();
```

`api.get()` returns a [`ResponsePromise`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-responsepromise), so `.json<User[]>()` reads and types the response body without first awaiting a separate response. The second call uses its request-level URL settings instead of the shared service path and returns the body as text.

For the URL input and resolution rules for `URL`, `Request`, `baseUrl`, `prefix`, and `searchParams`, see [Instances and defaults](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/instances-and-defaults). This page adds the request-level override pattern: keep shared defaults for ordinary calls, then supply different URL settings for one request.

## Derive a specialized client

Use `extend()` when a group of requests needs a variation of the shared client. Normal extension deep-merges options: headers merge, search parameters accumulate, and hooks append. Use [`replaceOption`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky) when the child must replace an inherited merged value rather than combine with it:

```ts title="users-api.ts"
import {replaceOption, type KyInstance} from 'ky';
import {api} from './api.js';

export const usersApi: KyInstance = api.extend({
	prefix: 'users/',
	headers: replaceOption({
		accept: 'application/json',
		'x-client': 'users-service',
	}),
});

async function loadUser(): Promise<void> {
	const response = await usersApi.get('42').text();
	console.log(response);
}

void loadUser();
```

The child keeps the parent's `baseUrl` and other defaults, uses `users/` as its prefix, and replaces the inherited header collection with the two headers shown. The original `api` remains unchanged.

These settings are fields of [`Options`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-options). The options that matter here are:

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `baseUrl` | `string \| URL` | — | Resolves relative inputs against a base URL. An absolute input bypasses it. |
| `prefix` | `string \| URL` | — | Prepends a string or URL to a string input before URL resolution. |
| `headers` | `KyHeadersInit` | — | Supplies HTTP headers; child instances merge them unless you use `replaceOption`. |
| `retry` | [`RetryOptions`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-retryoptions) `\| number` | — | Controls retry behavior; a number supplies the retry limit. |
| `timeout` | `number \| false` | — | Sets the per-attempt timeout in milliseconds. |
| `json` | `unknown` | — | Sends a JSON body instead of constructing `body` yourself. |

## Pitfalls

- Include a trailing slash in a path-bearing `baseUrl`, such as `/api/`, when page-relative inputs should extend that path rather than replace its last segment.
- `prefix` only affects string inputs. A `URL` or `Request` input does not receive it; a `Request` also ignores `baseUrl`.
- `extend()` does not replace merged values by default. Wrap a header, hook, search-parameter, context, or other merged option with `replaceOption` when the child must replace it.

## Related

- [`ky`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky) for the complete instance API and URL options.
- [`ky-options`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-options) for the full `Options` type.
