# ky

Tiny and elegant HTTP client based on the Fetch API

## Install

```bash
npm install ky
```

## On their own pages

- [`Hooks`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-hooks)
- [`HTTPError`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-httperror): Error thrown when the response has a non-2xx status code and `throwHttpErrors` is enabled.
- [`KyRequest`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-kyrequest)
- [`NormalizedOptions`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-normalizedoptions): Normalized options passed to the `fetch` call and hooks.
- [`Options`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-options): Options are the same as `window.fetch`, except for the KyOptions
- [`ResponsePromise`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-responsepromise)
- [`RetryOptions`](https://bench-ky-56.atloria.app/p/bench-ky-56-cVfxjVA4Qg/developer/ky-retryoptions)

## Functions

### `isForceRetryError`

Type guard to check if an error is a `ForceRetryError`.

```ts
function isForceRetryError(error: unknown): error is ForceRetryError
```

**Parameters**

- `error`: The error to check

**Returns** `true` if the error is a `ForceRetryError`, `false` otherwise

**Example**

```
import ky, {isForceRetryError} from 'ky';

const api = ky.extend({
	hooks: {
		beforeRetry: [
			({error, retryCount}) => {
				if (isForceRetryError(error)) {
					console.log(`Forced retry #${retryCount}: ${error.code}`);
				}
			}
		]
	}
});
```

### `isHTTPError`

Type guard to check if an error is an `HTTPError`.

```ts
function isHTTPError<T = unknown>(error: unknown): error is HTTPError<T>
```

**Parameters**

- `error`: The error to check

**Returns** `true` if the error is an `HTTPError`, `false` otherwise

**Example**

```
import ky, {isHTTPError} from 'ky';
try {
	const response = await ky.get('/api/data');
} catch (error) {
	if (isHTTPError(error)) {
		console.log('HTTP error status:', error.response.status);
	}
}
```

### `isKyError`

Type guard to check if an error is a `KyError`.

Note: `SchemaValidationError` is intentionally not considered a Ky error. `KyError` covers failures in Ky's HTTP lifecycle (bad status, timeout, retry), while schema validation errors originate from the user-provided schema, not from Ky itself.

```ts
function isKyError(error: unknown): error is KyError
```

**Parameters**

- `error`: The error to check

**Returns** `true` if the error is a Ky error, `false` otherwise

**Example**

```
import ky, {isKyError} from 'ky';
try {
	const response = await ky.get('/api/data');
} catch (error) {
	if (isKyError(error)) {
		// Handle Ky-specific errors
		console.log('Ky error occurred:', error.message);
	} else {
		// Handle other errors
		console.log('Unknown error:', error);
	}
}
```

### `isNetworkError`

Type guard to check if an error is a `NetworkError`.

```ts
function isNetworkError(error: unknown): error is NetworkError
```

**Parameters**

- `error`: The error to check

**Returns** `true` if the error is a `NetworkError`, `false` otherwise

**Example**

```
import ky, {isNetworkError} from 'ky';
try {
	const response = await ky.get('/api/data');
} catch (error) {
	if (isNetworkError(error)) {
		console.log('Network error:', error.request.url);
	}
}
```

### `isResponseSizeError`

**Not released yet.** It is in the source, not in the latest release on npm.

Type guard to check if an error is a `ResponseSizeError`.

```ts
function isResponseSizeError(error: unknown): error is ResponseSizeError
```

**Parameters**

- `error`: The error to check

**Returns** `true` if the error is a `ResponseSizeError`, `false` otherwise

**Example**

```
import ky, {isResponseSizeError} from 'ky';

try {
	await ky('https://example.com/data', {maxResponseSize: 1024}).json();
} catch (error) {
	if (isResponseSizeError(error)) {
		console.log(`Response exceeded ${error.maxResponseSize} bytes`);
	}
}
```

### `isTimeoutError`

Type guard to check if an error is a `TimeoutError`.

```ts
function isTimeoutError(error: unknown): error is TimeoutError
```

**Parameters**

- `error`: The error to check

**Returns** `true` if the error is a `TimeoutError`, `false` otherwise

**Example**

```
import ky, {isTimeoutError} from 'ky';
try {
	const response = await ky.get('/api/data', { timeout: 1000 });
} catch (error) {
	if (isTimeoutError(error)) {
		console.log('Request timed out:', error.request.url);
	}
}
```

## Classes

### `ForceRetryError`

Error used to signal a forced retry from `afterResponse` hooks.

This is thrown when `ky.retry()` is returned from an `afterResponse` hook. It is observable in `beforeRetry` and `beforeError` hooks via the `isForceRetryError()` type guard.

```ts
class ForceRetryError extends KyError
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` |  | `'ForceRetryError'` |  |
| `customDelay` | `number \| undefined` |  |  |
| `code` | `string \| undefined` |  |  |
| `customRequest` | `Request \| undefined` |  |  |
| `message` | `string` |  |  |
| `stack?` | `string` |  |  |

**Methods**

- `constructor(options?: ForceRetryOptions)`

### `KyError`

Base class for all Ky-specific errors. `HTTPError`, `NetworkError`, `TimeoutError`, `ResponseSizeError`, and `ForceRetryError` extend this class.

You can use `instanceof KyError` to check if an error originated from Ky, or use the `isKyError()` type guard for cross-realm compatibility and TypeScript type narrowing.

Note: `SchemaValidationError` is intentionally not considered a Ky error. `KyError` covers failures in Ky's HTTP lifecycle (bad status, timeout, retry), while schema validation errors originate from the user-provided schema, not from Ky itself.

```ts
class KyError extends Error
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` |  | `'KyError'` |  |
| `message` | `string` |  |  |
| `stack?` | `string` |  |  |

**Methods**

- `get isKyError(): true`

### `NetworkError`

Error thrown when a network error occurs during the request (e.g., DNS failure, connection refused, offline). It has a `request` property with the `Request` object. The original error is available via the standard `cause` property.

Network errors are automatically retried (for retriable methods). A connection that drops while a Ky shortcut method like `.json()` is reading the response body is also wrapped in `NetworkError`, but it is not retried because the response has already been received.

Note: Network errors are detected using runtime-specific heuristics. Unrecognized runtimes may produce errors that are not wrapped in `NetworkError`. Use the `shouldRetry` option to handle such cases.

```ts
class NetworkError extends KyError
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` |  | `'NetworkError'` |  |
| `request` | `KyRequest` |  |  |
| `message` | `string` |  |  |
| `stack?` | `string` |  |  |

**Methods**

- `constructor(request: Request, options?: {cause?: Error | undefined})`

### `ResponseSizeError`

**Not released yet.** It is in the source, not in the latest release on npm.

Error thrown when the response body exceeds `maxResponseSize`. It has a `request` property with the `Request` object and a `maxResponseSize` property with the configured limit in bytes.

```ts
class ResponseSizeError extends KyError
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` |  | `'ResponseSizeError'` |  |
| `request` | `KyRequest` |  |  |
| `maxResponseSize` | `number` |  |  |
| `message` | `string` |  |  |
| `stack?` | `string` |  |  |

**Methods**

- `constructor(request: Request, maxResponseSize: number)`

**Example**

```
import ky, {isResponseSizeError} from 'ky';

try {
	await ky('https://example.com/data', {maxResponseSize: 1024}).json();
} catch (error) {
	if (isResponseSizeError(error)) {
		console.log(`Response exceeded ${error.maxResponseSize} bytes`);
	}
}
```

### `SchemaValidationError`

The error thrown when [Standard Schema](https://github.com/standard-schema/standard-schema) validation fails in `.json(schema)`. It has an `issues` property with the validation issues from the schema.

This error intentionally does not extend `KyError` because it does not represent a failure in Ky's HTTP lifecycle. The request succeeded; the user's schema rejected the data. As such, it is not matched by `isKyError()`.

```ts
class SchemaValidationError extends Error
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` |  | `'SchemaValidationError'` |  |
| `issues` | `readonly StandardSchemaV1Issue[]` |  |  |
| `message` | `string` |  |  |
| `stack?` | `string` |  |  |

**Methods**

- `constructor(issues: readonly StandardSchemaV1Issue[])`

**Example**

```
import ky, {SchemaValidationError} from 'ky';
import {z} from 'zod';

const userSchema = z.object({name: z.string()});

try {
	const user = await ky('/api/user').json(userSchema);
	console.log(user.name);
} catch (error) {
	if (error instanceof SchemaValidationError) {
		console.error(error.issues);
	}
}
```

### `TimeoutError`

Error thrown when the request times out. It has a `request` property with the `Request` object.

```ts
class TimeoutError extends KyError
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `name` |  | `'TimeoutError'` |  |
| `request` | `KyRequest` |  |  |
| `message` | `string` |  |  |
| `stack?` | `string` |  |  |

**Methods**

- `constructor(request: Request)`

## Constants

### `default`

The package's default export: import it under a name of your own, `import ky from 'ky'`.

```ts
declare const ky: KyInstance
export default ky
```

### `replaceOption`

Wraps a value so that `ky.extend()` will replace the parent value instead of merging with it. Works with hooks, headers, search parameters, context, and any other deep-merged option.

By default, `.extend()` deep-merges options with the parent instance: hooks get appended, headers get merged, and search parameters get accumulated. Use `replaceOption` when you want to fully replace a merged property instead.

```ts
const replaceOption: <T>(value: T) => T
```

**Example**

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

const base = ky.create({
	hooks: {beforeRequest: [addAuth, addTracking]},
});

// Replaces instead of appending
const extended = base.extend({
	hooks: replaceOption({beforeRequest: [onlyThis]}),
});
// hooks.beforeRequest is now [onlyThis], not [addAuth, addTracking, onlyThis]
```

## Types

### `AfterResponseHook`

```ts
type AfterResponseHook = (state: AfterResponseState) => Response | RetryMarker | void | Promise<Response | RetryMarker | void>;
```

### `AfterResponseState`

```ts
type AfterResponseState = { … }
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `request` | `KyRequest` |  |  |
| `options` | `Readonly<NormalizedOptions>` |  |  |
| `response` | `KyResponse` |  |  |
| `retryCount` | `number` |  | The number of retries attempted. `0` for the initial request, increments with each retry. |

### `BeforeErrorHook`

```ts
type BeforeErrorHook = (state: BeforeErrorState) => Error | Promise<Error>;
```

### `BeforeErrorState`

```ts
type BeforeErrorState = { … }
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `request` | `KyRequest` |  |  |
| `options` | `Readonly<NormalizedOptions>` |  |  |
| `error` | `Error` |  |  |
| `retryCount` | `number` |  | The number of retries attempted. `0` for the initial request, increments with each retry. |

### `BeforeRequestHook`

```ts
type BeforeRequestHook = (state: BeforeRequestState) => Request | Response | void | Promise<Request | Response | void>;
```

### `BeforeRequestState`

```ts
type BeforeRequestState = { … }
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `request` | `KyRequest` |  |  |
| `options` | `Readonly<NormalizedOptions>` |  |  |
| `retryCount` | `0` |  | The number of retries attempted. Always `0`, since `beforeRequest` hooks run once before retry handling begins. |

### `BeforeRetryHook`

```ts
type BeforeRetryHook = (state: BeforeRetryState) => Request | Response | typeof stop | void | Promise<Request | Response | typeof stop | void>;
```

### `BeforeRetryState`

```ts
type BeforeRetryState = { … }
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `request` | `KyRequest` |  |  |
| `options` | `Readonly<NormalizedOptions>` |  |  |
| `error` | `Error` |  |  |
| `retryCount` | `number` |  | The number of retries attempted. Always `>= 1`, since this hook is only called during retries, not on the initial request. |

### `InitHook`

This hook enables you to modify the options before they are used to construct the request. The hook function receives the mutable options object and can modify it in place. You could, for example, modify `searchParams`, `headers`, or `json` here. The `headers` option is always a plain object with lowercase names, where a header removed with `undefined` keeps an `undefined` value.

Unlike other hooks, `init` hooks are synchronous. Any error thrown will propagate synchronously and will not be caught by `beforeError` hooks.

```ts
type InitHook = (options: InitOptions) => void;
```

**Example**

```
import ky from 'ky';

const api = ky.extend({
	hooks: {
		init: [
			options => {
				options.searchParams = {apiKey: getApiKey()};
			},
		],
	},
});

const response = await api.get('https://example.com/api/users');
// URL: https://example.com/api/users?apiKey=123
```

### `Input`

```ts
type Input = string | URL | Request;
```

**Members**

- `toString(): string` — Returns a string representation of a string.
- `valueOf(): string` — Returns the primitive value of the specified object.

### `KyInstance`

```ts
type KyInstance = { … }
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `get` | `<T>(url: Input, options?: Options) => ResponsePromise<T>` |  | Fetch the given `url` using the option `{method: 'get'}`. |
| `post` | `<T>(url: Input, options?: Options) => ResponsePromise<T>` |  | Fetch the given `url` using the option `{method: 'post'}`. |
| `put` | `<T>(url: Input, options?: Options) => ResponsePromise<T>` |  | Fetch the given `url` using the option `{method: 'put'}`. |
| `delete` | `<T>(url: Input, options?: Options) => ResponsePromise<T>` |  | Fetch the given `url` using the option `{method: 'delete'}`. |
| `patch` | `<T>(url: Input, options?: Options) => ResponsePromise<T>` |  | Fetch the given `url` using the option `{method: 'patch'}`. |
| `head` | `(url: Input, options?: Options) => ResponsePromise` |  | Fetch the given `url` using the option `{method: 'head'}`. |
| `query` | `<T>(url: Input, options?: Options) => ResponsePromise<T>` |  | Fetch the given `url` using the option `{method: 'query'}`. |
| `create` | `(defaultOptions?: Options) => KyInstance` |  | Create a new Ky instance with complete new defaults, without inheriting from any parent instance. |
| `extend` | `(defaultOptions: Options \| ((parentOptions: Options) => Options)) => KyInstance` |  | Create a new Ky instance with some defaults overridden with your own. |
| `stop` | `typeof stop` |  | A `Symbol` that can be returned by a `beforeRetry` hook to stop the retry. This will also short circuit the remaining `beforeRetry` hooks. |
| `retry` | `typeof retry` |  | Force a retry from an `afterResponse` hook. |

### `KyResponse`

```ts
type KyResponse<T = unknown> = {
	clone: () => KyResponse<T>;
	json: <J = T>() => Promise<J>;
} & Response;
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `clone` | `() => KyResponse<T>` |  |  |
| `json` | `<J = T>() => Promise<J>` |  |  |
| `headers` | `Headers` |  | The **`headers`** read-only property of the with the response. |
| `ok` | `boolean` |  | The **`ok`** read-only property of the Response interface contains a Boolean stating whether the response was successful (status in the range 200-299) or not. |
| `redirected` | `boolean` |  | The **`redirected`** read-only property of the Response interface indicates whether or not the response is the result of a request you made which was redirected. |
| `status` | `number` |  | The **`status`** read-only property of the Response interface contains the HTTP status codes of the response. |
| `statusText` | `string` |  | The **`statusText`** read-only property of the Response interface contains the status message corresponding to the HTTP status code in Response.status. |
| `type` | `ResponseType` |  | The **`type`** read-only property of the Response interface contains the type of the response. |
| `url` | `string` |  | The **`url`** read-only property of the Response interface contains the URL of the response. |
| `body` | `ReadableStream<Uint8Array<ArrayBuffer>> \| null` |  | [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) |
| `bodyUsed` | `boolean` |  | [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) |

**Members**

- `arrayBuffer(): Promise<ArrayBuffer>` — [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer)
- `blob(): Promise<Blob>` — [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob)
- `bytes(): Promise<Uint8Array<ArrayBuffer>>` — [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes)
- `formData(): Promise<FormData>` — [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/formData)
- `text(): Promise<string>` — [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text)

### `Progress`

```ts
type Progress = { … }
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `percent` | `number` |  | A number between `0` and `1` representing the progress percentage. |
| `transferredBytes` | `number` |  | The number of bytes transferred so far. |
| `totalBytes` | `number` |  | The total number of bytes to be transferred. This is an estimate and may be `0` for an empty transfer or when the total size cannot be determined. |

### `SearchParamsOption`

```ts
type SearchParamsOption =
	| Exclude<SearchParamsInit, string[][]>
	| Record<string, string | number | boolean | undefined>
	| Array<Array<string | number | boolean>>
	| ReadonlyArray<ReadonlyArray<string | number | boolean>>;
```

**Members**

- `toString(): string` — Returns a string representation of a string.
- `valueOf(): string` — Returns the primitive value of the specified object.

### `ShouldRetryState`

```ts
type ShouldRetryState = { … }
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `error` | `Error` |  | The error that caused the request to fail. |
| `retryCount` | `number` |  | The number of retries attempted. Starts at 1 for the first retry. |

### `StandardSchemaV1`

```ts
type StandardSchemaV1<InputType = unknown, OutputType = InputType> = { … }
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `'~standard'` | `{ readonly version: 1; readonly vendor: string; readonly validate: ( value: unknown, options?: StandardSchemaV1Options, ) => StandardSchemaV1Result<OutputType> \| Promise<StandardSchemaV1Result<OutputType>>; readonly types?: StandardSchemaV1Types<InputType, OutputType> \| undefined; }` |  |  |
| `'~standard'` | `{ readonly version: 1; readonly vendor: string; readonly validate: ( value: unknown, options?: StandardSchemaV1Options, ) => StandardSchemaV1Result<OutputType> \| Promise<StandardSchemaV1Result<OutputType>>; readonly types?: StandardSchemaV1Types<InputType, OutputType> \| undefined; }` |  |  |

### `StandardSchemaV1InferOutput`

```ts
type StandardSchemaV1InferOutput<Schema extends StandardSchemaV1> = Schema['~standard'] extends {
	readonly types: StandardSchemaV1Types<unknown, infer OutputType>;
}
	? OutputType
	: Extract<
		Awaited<ReturnType<Schema['~standard']['validate']>>,
		StandardSchemaV1SuccessResult<unknown>
	> extends StandardSchemaV1SuccessResult<infer OutputType>
		? OutputType
		: unknown;
```

### `StandardSchemaV1Issue`

```ts
type StandardSchemaV1Issue = { … }
```

**Properties**

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `message` | `string` |  |  |
| `path?` | `ReadonlyArray<PropertyKey \| {readonly key: PropertyKey}> \| undefined` |  |  |
