Ky
🌳 Tiny & elegant JavaScript HTTP client based on the Fetch API
Install / Use
/learn @sindresorhus/KyREADME
Ky is a tiny and elegant HTTP client based on the Fetch API
Ky targets modern browsers, Node.js, Bun, and Deno.
It's just a tiny package with no dependencies.
Benefits over plain fetch
- Simpler API
- Method shortcuts (
ky.post()) - Treats non-2xx status codes as errors (after redirects)
- Retries failed requests
- JSON option
- Timeout support
- Base URL option
- Instances with custom defaults
- Hooks
- TypeScript niceties (e.g.
.json()supports generics and defaults tounknown, notany)
Install
npm install ky
[!NOTE] This readme is for the next version of Ky. For the current version, click here.
CDN
Usage
import ky from 'ky';
const json = await ky.post('https://example.com', {json: {foo: true}}).json();
console.log(json);
//=> {data: '🦄'}
With plain fetch, it would be:
class HTTPError extends Error {}
const response = await fetch('https://example.com', {
method: 'POST',
body: JSON.stringify({foo: true}),
headers: {
'content-type': 'application/json'
}
});
if (!response.ok) {
throw new HTTPError(`Fetch error: ${response.statusText}`);
}
const json = await response.json();
console.log(json);
//=> {data: '🦄'}
If you are using Deno, import Ky from a URL. For example, using a CDN:
import ky from 'https://esm.sh/ky';
API
ky(input, options?)
The input and options are the same as fetch, with additional options available (see below).
Returns a Response object with Body methods added for convenience. So you can, for example, call ky.get(input).json() directly without having to await the Response first. When called like that, an appropriate Accept header will be set depending on the body method used. Unlike the Body methods of window.Fetch, these will throw an HTTPError if the response status is not in the range of 200...299. Also, .json() will return undefined if body is empty or the response status is 204 instead of throwing a parse error due to an empty body.
Available body shortcuts: .json(), .text(), .formData(), .arrayBuffer(), .blob(), and .bytes(). The .bytes() shortcut is only present when the runtime supports Response.prototype.bytes().
import ky from 'ky';
const user = await ky('/api/user').json();
console.log(user);
⌨️ TypeScript: Accepts an optional type parameter, which defaults to unknown, and is passed through to the return type of .json().
import ky from 'ky';
// user1 is unknown
const user1 = await ky('/api/users/1').json();
// user2 is a User
const user2 = await ky<User>('/api/users/2').json();
// user3 is a User
const user3 = await ky('/api/users/3').json<User>();
console.log([user1, user2, user3]);
You can also get the response body as JSON and validate it with a Standard Schema compatible validator (for example, Zod 3.24+). This throws a SchemaValidationError when validation fails.
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);
}
}
// Get raw bytes (when supported by the runtime)
const bytes = await ky('/api/file').bytes();
console.log(bytes instanceof Uint8Array);
ky.get(input, options?)
ky.post(input, options?)
ky.put(input, options?)
ky.patch(input, options?)
ky.head(input, options?)
ky.delete(input, options?)
Sets options.method to the method name and makes a request.
⌨️ TypeScript: Accepts an optional type parameter for use with JSON responses (see ky()).
input
Type: string | URL | Request
Same as fetch input.
When using a Request instance as input, any URL altering options (such as baseUrl) will be ignored.
options
Type: object
Same as fetch options, plus the following additional options:
method
Type: string
Default: 'get'
HTTP method used to make the request.
Internally, the standard methods (GET, POST, PUT, PATCH, HEAD and DELETE) are uppercased in order to avoid server errors due to case sensitivity.
json
Type: object and any other value accepted by JSON.stringify()
Shortcut for sending JSON. Use this instead of the body option. Accepts any plain object or value, which will be JSON.stringify()'d and sent in the body with the correct header set.
searchParams
Type: string | object<string, string | number | boolean | undefined> | Array<Array<string | number | boolean>> | URLSearchParams
Default: ''
Search parameters to include in the request URL. Setting this will override all existing search parameters in the input URL.
Accepts any value supported by URLSearchParams().
When passing an object, undefined values are automatically filtered out, while null values are preserved and converted to the string 'null'.
baseUrl
Type: string | URL
A base URL to resolve the input against. When the input (after applying the prefix option) is only a relative URL, such as 'users', '/users', or '//my-site.com', it will be resolved against the baseUrl to determine the destination of the request. Otherwise, the input is absolute, such as 'https://my-site.com', and it will bypass the baseUrl.
Useful when used with ky.extend() to create niche-specific Ky-instances.
If the baseUrl itself is relative, it will be resolved against the environment's base URL, such as document.baseURI in browsers or location.href in Deno (see the --location flag).
Tip: When setting a baseUrl that has a path, we recommend that it include a trailing slash /, as in '/api/' rather than /api. This ensures more intuitive behavior for page-relative input URLs, such as 'users' or './users', where they will extend from the full path of the baseUrl rather than replacing its last path segment.
import ky from 'ky';
// On https://example.com
const response = await ky('users', {baseUrl: '/api/'});
//=> 'https://example.com/api/users'
const response = await ky('/users', {baseUrl: '/api/'});
//=> 'https://example.com/users'
prefix
Type: string | URL
A prefix to prepend to the input before making the request (and before it is resolved against the baseUrl). It can be any valid path or URL, either relative or absolute. A trailing slash / is optional and will be added automatically, if needed, when it is joined with input. Only takes effect when input is a string.
Useful when used with ky.extend() to create niche-specific Ky-instances.
In most cases, you should use the baseUrl option instead, as it is more consistent with web standards. However, prefix is useful if you want origin-relative input URLs, such as /users, to be treated as if they were page-relative. In other words, the leading slash of the input will essentially be ignored, because the prefix will become part of the input before URL resolution happens.
import ky from 'ky';
// On https://example.com
const response = await ky('users', {prefix: '/api/'});
//=> 'https://example.com/api/users'
const response = await ky('/users', {prefix: '/api/'});
//=> 'https://example.com/api/users'
Notes:
- The
prefixandinputare joined with a slash/, and slashes are normalized at the join boundary by trimming trailing slashes fromprefixand leading slashes frominput. - After
prefixandinputare joined, the result is resolved against thebaseUrloption, if present.
retry
Type: object | number
Default:
Related Skills
bluebubbles
335.4kUse when you need to send or manage iMessages via BlueBubbles (recommended iMessage integration). Calls go through the generic message tool with channel="bluebubbles".
gh-issues
335.4kFetch GitHub issues, spawn sub-agents to implement fixes and open PRs, then monitor and address PR review comments. Usage: /gh-issues [owner/repo] [--label bug] [--limit 5] [--milestone v1.0] [--assignee @me] [--fork user/repo] [--watch] [--interval 5] [--reviews-only] [--cron] [--dry-run] [--model glm-5] [--notify-channel -1002381931352]
healthcheck
335.4kHost security hardening and risk-tolerance configuration for OpenClaw deployments
node-connect
335.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
