Skip to content

Releases: sindresorhus/ky

v2.0.0

06 Apr 08:02

Choose a tag to compare

Breaking

  • Require Node.js 22 f1da0fc
  • Unify hook signatures around a single state object (#827) ecdd45e
    • All hooks now receive a single {request, options, retryCount, ...} state object instead of separate arguments.
  • Rename prefixUrl to prefix, and allow leading slashes in input (#606) 1f2ad7f
  • Make beforeError hook receive all errors, not just HTTPError (#829) 101c74b
  • Make .json() throw on empty bodies and 204 responses instead of returning an empty string (#854) 1b8e1ff
  • Merge searchParams with input URL instead of replacing (#840) 29e78fe
  • Strip Ky-specific properties from normalized options passed to hooks (#826) 433febd
  • Treat hook errors as fatal outside retry handling (#834) 90c6d00

New

  • Add totalTimeout option for an overall timeout across all retries (#848) c20d7c7
  • Add baseUrl option for standard URL resolution (#606) 1f2ad7f
    • baseUrl uses standard URL resolution: /users means origin-root, users means relative to the base path.
    • prefix does simple string joining first, so /users and users both append to the prefix the same way. Use it only when you want that behavior.
  • Add data property to HTTPError with pre-parsed response body (#823) 1341f5c
    • The response body is automatically consumed and parsed, fixing resource leaks and making error details immediately available without awaiting (#642).
  • Add init hook (#841) 87c6740
  • Add NetworkError class and tighten retry logic (#842) eaf0b80
  • Add Standard Schema validation for .json() (#830) 94741a9
  • Add replaceOption helper for .extend() (#846) bb8412e
  • Add request and options to beforeError hook state (#835) 01e0b85
  • Add request/response context to parseJson option (#849) 3713ce8
  • Don't throw HTTPError for opaque responses from no-cors requests (#847) 1d15eb6
  • Gracefully ignore onUploadProgress when request streams are unsupported (#845) 1e38ff4

Fixes

  • Fix beforeRequest hooks being skipped when a Request is returned (#832) aec65db
  • Ignore non-Errors returned by beforeError hooks (#833) a541fc0

Migration guide

Hook signatures

All hooks now receive a single state object instead of separate arguments.

hooks: {
-	beforeRequest: [(request, options) => {
+	beforeRequest: [({request, options}) => {
		request.headers.set('X-Custom', 'value');
	}],
-	afterResponse: [(request, options, response) => {
+	afterResponse: [({request, options, response}) => {
		log(response.status);
	}],
-	beforeRetry: [({request, options, error, retryCount}) => {
+	beforeRetry: [({request, options, error, retryCount}) => {
		// Same as before - beforeRetry already used an object
	}],
-	beforeError: [(error) => {
+	beforeError: [({error}) => {
		return error;
	}],
}

prefixUrl renamed to prefix

-ky('users', {prefixUrl: 'https://example.com/api/'});
+ky('users', {prefix: 'https://example.com/api/'});

Leading slashes in input are now allowed with prefix. There's also a new baseUrl option for standard URL resolution, which you may prefer over prefix.

beforeError hook receives all errors

Previously only received HTTPError. Now receives all error types. Use type guards:

+import {isHTTPError} from 'ky';

hooks: {
	beforeError: [
-		(error) => {
-			const {response} = error;
+		({error}) => {
+			if (isHTTPError(error)) {
+				const {response} = error;
+			}

			return error;
		}
	]
}

.json() on empty responses

.json() now throws a parse error on empty bodies and 204 responses instead of returning an empty string. This aligns with native JSON.parse behavior and surfaces the real issue; your code expected JSON but the server sent none.

Check the status before calling .json() if you expect empty responses:

const response = await ky('https://example.com/api');

if (response.status !== 204) {
    const data = await response.json();
}

Ky-specific options stripped from hooks

Ky-specific properties (hooks, json, parseJson, stringifyJson, searchParams, timeout, throwHttpErrors, fetch) are no longer available on the options object passed to hooks. If you need access to these values, store them in a variable outside the hook or use the context option to pass data between hooks.

searchParams merging

searchParams now merges with existing query parameters in the input URL instead of replacing them.

// v1: searchParams replaces ?existing=1
// v2: searchParams merges with ?existing=1
ky('https://example.com?existing=1', {searchParams: {added: 2}});
// => https://example.com?existing=1&added=2

HTTPError response body

error.response.json() and other body methods no longer work since the body is now automatically consumed. Use error.data instead, which has the pre-parsed response body immediately available.

-const body = await error.response.json();
-console.log(body.message);
+console.log(error.data.message);

This fixes resource leaks when catching HTTPError without consuming the body (#633) and makes error details synchronously available (#642). We considered cloning the response to keep both paths working, but that doubles memory usage for error bodies and adds edge cases around locked/large streams for little benefit. error.response is still available for headers and status.

Upgrading from 2.0.0-0

.json() on empty responses

The behavior changed again from the prerelease. .json() now throws instead of returning undefined for empty bodies and 204 responses. The return type is back to Promise<T> (no more | undefined).


Thanks to @sholladay for helping with this update.


v1.14.3...v2.0.0

v2.0.0-0

29 Mar 10:59

Choose a tag to compare

v2.0.0-0 Pre-release
Pre-release

This is a prerelease of the next major version. Please try it out and report any issues.

npm install ky@next

Breaking

  • Require Node.js 22 f1da0fc
  • Unify hook signatures around a single state object (#827) ecdd45e
    • All hooks now receive a single {request, options, retryCount, ...} state object instead of separate arguments.
  • Rename prefixUrl to prefix, and allow leading slashes in input (#606) 1f2ad7f
  • Make beforeError hook receive all errors, not just HTTPError (#829) 101c74b
  • Return undefined instead of empty string from .json() on empty responses (#828) 51e3552
  • Merge searchParams with input URL instead of replacing (#840) 29e78fe
  • Strip Ky-specific properties from normalized options passed to hooks (#826) 433febd
  • Treat hook errors as fatal outside retry handling (#834) 90c6d00

New

Fixes

  • Fix beforeRequest hooks being skipped when a Request is returned (#832) aec65db
  • Ignore non-Errors returned by beforeError hooks (#833) a541fc0

Migration guide

Hook signatures

All hooks now receive a single state object instead of separate arguments.

hooks: {
-	beforeRequest: [(request, options) => {
+	beforeRequest: [({request, options}) => {
		request.headers.set('X-Custom', 'value');
	}],
-	afterResponse: [(request, options, response) => {
+	afterResponse: [({request, options, response}) => {
		log(response.status);
	}],
-	beforeRetry: [({request, options, error, retryCount}) => {
+	beforeRetry: [({request, options, error, retryCount}) => {
		// Same as before - beforeRetry already used an object
	}],
-	beforeError: [(error) => {
+	beforeError: [({error}) => {
		return error;
	}],
}

prefixUrl renamed to prefix

-ky('users', {prefixUrl: 'https://example.com/api/'});
+ky('users', {prefix: 'https://example.com/api/'});

Leading slashes in input are now allowed with prefix. There's also a new baseUrl option for standard URL resolution, which you may prefer over prefix.

beforeError hook receives all errors

Previously only received HTTPError. Now receives all error types. Use type guards:

+import {isHTTPError} from 'ky';

hooks: {
	beforeError: [
-		(error) => {
-			const {response} = error;
+		({error}) => {
+			if (isHTTPError(error)) {
+				const {response} = error;
+			}

			return error;
		}
	]
}

.json() on empty responses

The return type changed from Promise<T> to Promise<T | undefined>. TypeScript users will need to handle the undefined case.

const data = await ky('https://example.com/api').json();
-// Previously: '' (empty string) for 204 responses
+// Now: undefined for 204 responses

Ky-specific options stripped from hooks

Ky-specific properties (hooks, json, parseJson, stringifyJson, searchParams, timeout, throwHttpErrors, fetch) are no longer available on the options object passed to hooks. If you need access to these values, store them in a variable outside the hook or use the context option to pass data between hooks.

searchParams merging

searchParams now merges with existing query parameters in the input URL instead of replacing them.

// v1: searchParams replaces ?existing=1
// v2: searchParams merges with ?existing=1
ky('https://example.com?existing=1', {searchParams: {added: 2}});
// => https://example.com?existing=1&added=2

HTTPError response body

error.response.json() and other body methods no longer work since the body is now automatically consumed. Use error.data instead, which has the pre-parsed response body immediately available.

-const body = await error.response.json();
-console.log(body.message);
+console.log(error.data.message);

This fixes resource leaks when catching HTTPError without consuming the body (#633) and makes error details synchronously available (#642). We considered cloning the response to keep both paths working, but that doubles memory usage for error bodies and adds edge cases around locked/large streams for little benefit. error.response is still available for headers and status.


v1.14.3...v2.0.0-0

v1.14.3

25 Jan 17:30

Choose a tag to compare

  • Fix empty context object being added to merged json body d7f7e73

v1.14.2...v1.14.3

v1.14.2

26 Dec 15:34

Choose a tag to compare

  • Fix response body cleanup after hooks (#809) 2bdbd59
  • Avoid awaiting response cancel on forced retry (#806) e673eaa
  • Make isKyError type guard include ForceRetryError (#801) 5791b66
  • Fix handling of uppercase retry.methods and improve typing (#789) cec10a9

v1.14.1...v1.14.2

v1.14.1

07 Dec 14:37

Choose a tag to compare

  • Fix: Handle backoffLimit being undefined (#796) 4fd8c28

v1.14.0...v1.14.1

v1.14.0

04 Nov 06:49

Choose a tag to compare


v1.13.0...v1.14.0

v1.13.0

22 Oct 10:44

Choose a tag to compare

Improvements

Fixes

  • Fix Next.js edge runtime next option support (#775) caf78d2
  • Fix compatibility with a non-standard Retry-After header (#771) c488be0

v1.12.0...v1.13.0

v1.12.0

14 Oct 07:52

Choose a tag to compare

Improvements

  • Add retry count to afterResponse and beforeError hooks (#763) 37fc250
  • Add retry count to beforeRequest hook (#754) b7572d0
  • Add type guards for errors (#746) 7e1fd0b

Fixes

  • Fix dispatcher option being ignored (#757) 5a3fc90
  • Fix hooks field incorrectly included in NormalizedOptions (#726) (#756) 0f2f00a
  • Fix searchParams option merging with URLSearchParams instances (#755) c847eec
  • Fix signal merging when using both instance and request options (#751) 2b0a100
  • Fix FormData content-type boundary regeneration when retrying in hooks (#752) d02b636

v1.11.0...v1.12.0

v1.11.0

27 Sep 12:14

Choose a tag to compare

  • Improve progress events for onDownloadProgress and onUploadProgress (#736) 60958f9
  • Fix hang on stream request cleanup in Node.js (#741) 9a89a35

v1.10.0...v1.11.0

v1.10.0

05 Sep 05:52

Choose a tag to compare

  • Support undefined values for searchParams object (#729) a87d9e9
  • Fix race condition issue with .json() (#731) 8cd79d3

v1.9.1...v1.10.0