# Add QUERY method support to ky

ky can now issue HTTP QUERY requests, a method that takes a body where GET cannot. The change is four lines, split between the method allow-list and the normalizer that uppercases it.

| | |
| --- | --- |
| Status | MERGED |
| Source | github:sindresorhus/ky#873 |
| Original | https://github.com/sindresorhus/ky/pull/873 |
| Workspace | Showcase |
| Tags | typescript, http, fetch, api-design |

## What it does

**ky gains a query() helper so callers can send a body with a read-only request.**

HTTP QUERY behaves like GET but accepts a request body. Search endpoints want it because a complex filter does not fit comfortably in a URL, and ky rejected it before a request was ever built.

ky keeps an allow-list of HTTP methods. Anything outside it failed at the client, which meant QUERY never reached the server to be rejected on its own terms.

This adds QUERY to that list and exposes ky.query(), matching the shape of the existing helpers. Nothing about body or search-param serialization changed, so a QUERY request is assembled by the same code that assembles a POST.

- **One new public method.** query() joins get, post, put, patch, head, and delete, taking the same options object.
- **The normalizer is the real edit.** normalizeRequestMethod only uppercases methods it recognizes. QUERY had to be added there too, not just to the allow-list.
- **One array is both type and runtime.** requestMethods is the allow-list and the source of the RequestMethod union, so the two cannot drift.

**Diagram: Where a QUERY call stopped before, and where it goes now**

- BEFORE
  - ky('/search', …) (method: 'QUERY') [neutral]
  - rejected at the client (not in the allow-list) [bad]
  - ky('/search', …) -> rejected at the client
- AFTER
  - ky.query('/search') [accent]
  - normalizeRequestMethod (source/utils/normalize.ts) [neutral]
  - fetch(request) (body preserved) [good]
  - ky.query('/search') -> normalizeRequestMethod
  - normalizeRequestMethod -> fetch(request) : uppercased

## How it works

**There is one place a method name becomes what fetch receives.**

That choke point is why the change is four lines instead of forty.

```typescript
export const normalizeRequestMethod = (input: string): string =>
	requestMethods.includes(input as RequestMethod)
		? input.toUpperCase()
		: input;
```

_A method missing from requestMethods passes through as typed. That is how a lowercase 'query' would have reached fetch unchanged and failed somewhere far less obvious._

| | Before | After |
| --- | --- | --- |
| Allowed methods | get, post, put, patch, head, delete | …and query |
| Calling with QUERY | Throws before a request exists | Builds the request and sends the body |
| RequestMethod union | Six members | Seven |

## File map

**Ten files, two of which carry behaviour.**

The rest are tests, types, and docs following along.

| File | Role | Change | Risk |
| --- | --- | --- | --- |
| `source/core/constants.ts` | allow-list and union type | Adds 'query' to requestMethods, widening RequestMethod. | medium |
| `source/index.ts` | instance construction | Attaches query() beside the other method helpers. | medium |
| `test/methods.ts` | test coverage | Adds a QUERY round trip against a local server. | none |
| `readme.md` | documentation | Lists the method in the API table. | none |

## Receipts

**Each claim above, and what in the source supports it.**

Quoted from the diff, not inferred from the title.

- **Claim.** QUERY was rejected at the client before this change.
  - Evidence: requestMethods did not contain 'query', and normalizeRequestMethod only uppercases members of that array.
  - Where: source/core/constants.ts
- **Claim.** The change is additive for existing methods.
  - Evidence: One array entry and one helper added; no existing branch modified.
  - Where: 10 files changed, +118 −7
- **Claim.** A QUERY request carries a body.
  - Evidence: The added test sends a JSON body and asserts the server receives it with method QUERY.
  - Where: test/methods.ts

## Where to attack

**The risk is not the method. It is the widened union.**

Adding a member to an array that is also a type changes two things, and only one of them has a test.

- **QUERY is still a proposed method** (medium)
  - Not every proxy or CDN routes an unrecognized method. A caller can now build a request that fails in the middle of someone else's infrastructure rather than at the client.
  - Mitigation: Say in the docs that QUERY needs server support. The library cannot detect it.
- **The type change is untested** (low)
  - The new test covers the runtime path. Nothing asserts RequestMethod now includes 'query', so a later refactor could narrow it back without failing CI.
  - Mitigation: Add a type-level assertion beside the runtime test.

## What this brief could not check

- This brief was written by hand as a design fixture. It is modelled on real work, but no model read a diff to produce it, and its claims should not be relied on.
- CI output was not read, so nothing here rests on a check result.
