Ky is a tiny, modern HTTP client for JavaScript, built on top of the native Fetch API. It works in browsers, Node.js, Bun, Deno, and Web Workers, weighs around 7 KB minified + gzipped, and provides shortcuts for JSON, automatic retries, timeouts, and hooks all with a clean, promise-based API. If you’ve been writing fetch wrappers by hand or are looking for a lighter alternative to Axios, Ky is one of the best options available today.

What Is Ky?

 

Ky is an open-source JavaScript HTTP client created by Sindre Sorhus and maintained on GitHub. Unlike Axios, which is built on top of XMLHttpRequest in the browser and the http module in Node, Ky is built entirely on top of the standard Fetch API. That means smaller bundle size, no dependencies, and a runtime that works anywhere Fetch works.

Key facts:

  • Built on the Fetch API (no XHR, no Node http module)
  • MIT-licensed, maintained by Sindre Sorhus
  • Around 7 KB minified + gzipped
  • Zero dependencies
  • Works in browsers, Node.js 18+, Bun, Deno, Web Workers, Cloudflare Workers
  • ESM-only (use a bundler or native ESM)
  • First-class TypeScript support
  • GitHub: github.com/sindresorhus/ky
  • npm: npmjs.com/package/ky

Ky vs Axios vs Native Fetch – Quick Comparison

(W CMS wstaw jako prawdziwą tabelę z 4 kolumnami: Feature / Native fetch / Axios / Ky)

Feature: Bundle size Native fetch: 0 KB (built-in) Axios: ~13 KB gzipped Ky: ~7 KB gzipped

Feature: Runtime Native fetch: Browsers, Node 18+, Deno, Bun Axios: Browsers (XHR), Node (http), Deno, Bun Ky: Browsers, Node 18+, Deno, Bun, CF Workers

Feature: Automatic JSON Native fetch: No — manual .json() Axios: Yes Ky: Yes — .json() helper

Feature: Throws on non-2xx Native fetch: No (only on network error) Axios: Yes Ky: Yes

Feature: Built-in retries Native fetch: No Axios: No (needs plugin) Ky: Yes — configurable

Feature: Timeouts Native fetch: Only via AbortController Axios: Yes Ky: Yes — timeout option

Feature: Request hooks / interceptors Native fetch: No Axios: Interceptors Ky: beforeRequest, afterResponse, beforeError, beforeRetry

Feature: Dependencies Native fetch: None Axios: 1 (follow-redirects) Ky: None

Feature: TypeScript Native fetch: Yes (built-in lib.dom) Axios: Yes Ky: Yes — first-class

In short: pick native fetch when you need zero footprint and don’t mind writing your own ergonomics. Pick Axios when you support older browsers or Node versions that lack Fetch. Pick Ky when you want a small, modern, fetch-based client with the conveniences of Axios.

Installing Ky

npm install ky

Or with pnpm / yarn / bun:

pnpm add ky yarn add ky bun add ky

Ky is ESM-only. In Node.js use import ky from 'ky' from a module-type file or transpile through your bundler. There is no UMD build.

Examples of Using Ky

1. Basic GET Request

Ky makes fetching JSON as simple as it gets:

import ky from 'ky’;

const data = await ky.get(’https://jsonplaceholder.typicode.com/todos/1′).json(); console.log(data);

Why it’s great: Ky parses the response as JSON for you and throws an error if the status is not 2xx — no manual .ok check.

2. POST Request with JSON Payload

import ky from 'ky’;

const post = await ky.post(’https://jsonplaceholder.typicode.com/posts’, { json: { title: 'My New Post’, body: 'This is the content.’, userId: 1, }, }).json();

console.log(post);

Why it’s great: passing a json object automatically sets Content-Type: application/json and serializes the body — no manual JSON.stringify.

3. Graceful Error Handling

import ky, { HTTPError } from 'ky’;

try { const data = await ky.get(’https://jsonplaceholder.typicode.com/invalid’).json(); console.log(data); } catch (error) { if (error instanceof HTTPError) { console.error(’Status:’, error.response.status); const body = await error.response.json().catch(() => null); console.error(’Body:’, body); } else { console.error(’Network or parse error:’, error); } }

Why it’s great: Ky exports a typed HTTPError class that gives you access to the full failed Response object — perfect for surfacing server-side error payloads to your UI.

4. Adding Custom Headers (Authorization)

import ky from 'ky’;

const data = await ky.get(’https://api.example.com/me’, { headers: { Authorization: 'Bearer YOUR_ACCESS_TOKEN’, }, }).json();

console.log(data);

5. Timeouts and Retries

import ky from 'ky’;

const data = await ky.get(’https://api.example.com/slow’, { timeout: 5000, retry: { limit: 3, methods: [’get’], statusCodes: [408, 429, 500, 502, 503, 504], }, }).json();

Why it’s great: retries are status-aware and respect the Retry-After header by default. The default retry limit is 2 and Ky already includes sensible status codes — so retry: 3 is enough for most apps.

6. Hooks – Ky’s killer feature

Hooks let you transform requests, responses, and errors globally:

import ky from 'ky’;

const api = ky.create({ prefixUrl: ’https://api.example.com’, hooks: { beforeRequest: [ request => { const token = localStorage.getItem(’token’); if (token) request.headers.set(’Authorization’, Bearer ${token}); }, ], afterResponse: [ async (request, options, response) => { if (response.status === 401) { await refreshToken(); return ky(request); } }, ], }, });

const me = await api.get(’me’).json();

Why it’s great: hooks let you handle auth refresh, logging, request signing, or response normalization in one place — without polluting every call site. Native fetch doesn’t have this. Axios has interceptors, but Ky’s hooks API is narrower and easier to reason about.

7. Reusable instances with ky.create

If your app talks to one API a lot, create a configured instance:

import ky from 'ky’;

const api = ky.create({ prefixUrl: ’https://api.example.com’, timeout: 10_000, retry: 2, headers: { 'X-Client’: 'fireup-web’ }, });

const users = await api.get(’users’).json(); const created = await api.post(’users’, { json: { name: 'Ada’ } }).json();

Note: with prefixUrl, paths must not start with /.

When to use Ky (and when not to)

Ky is a strong fit if you:

  • Target modern runtimes (Node 18+, evergreen browsers, Bun, Deno, edge runtimes)
  • Want a small bundle and zero dependencies
  • Use TypeScript and want strong types out of the box
  • Need retries, timeouts, hooks, and JSON shortcuts without writing them yourself
  • Are migrating away from Axios for bundle-size or runtime reasons

Ky is less of a fit if you:

  • Need to support legacy browsers (IE 11, very old Safari) — those need a Fetch polyfill plus a non-ESM build
  • Run on Node.js < 18 (Fetch is not built in there)
  • Depend heavily on Axios-specific features like the global axios.defaults or transformRequest / transformResponse
  • Need a CommonJS build (Ky is ESM-only)

How to migrate from Axios to Ky

For most cases the migration is mechanical:

axios.get(url) becomes ky.get(url).json() axios.post(url, data) becomes ky.post(url, { json: data }).json() axios.create({ baseURL }) becomes ky.create({ prefixUrl }) axios.interceptors.request becomes hooks.beforeRequest axios.interceptors.response becomes hooks.afterResponse error.response.data is reached through await error.response.json() after catching HTTPError

The most common breaking changes: prefixUrl requires paths without a leading slash, and Ky throws an HTTPError instead of returning a response object on 4xx/5xx.

[H2] Need help building reliable APIs and frontends?

We’ve built production JavaScript and TypeScript projects at fireup.pro for years – from small SPAs to large platforms. If you’re choosing an HTTP client for a new project, refactoring legacy fetch wrappers, or migrating from Axios, talk to an expert: https://fireup.pro/lets-talk

Further reading on fireup.pro:

External references: