Use ky to send a JSON body and consume the response in one expression. Pass a type parameter when you know the response shape; pass a Standard Schema when you need runtime validation.
The examples use absolute URLs and run in the browser or Node.js. Replace https://api.example.com with your service's origin. Install Ky in your project:
bashnpm install ky
1. Send JSON and read the response
Send an array when your endpoint accepts a batch of records; see the quick start for basic JSON serialization and headers.
tsimport ky from 'ky';
async function main() {
const result = await ky.post('https://api.example.com/users', {
json: [{name: 'Ada'}, {name: 'Grace'}],
}).json();
console.log(result);
}
main().catch(console.error);
This sends two records in one JSON array and logs your service's parsed response; see How Ky works for the single-record example and the quick start for shortcut behavior and the default response type.
2. Give the response a TypeScript type
Use .json<User>() to get a Promise<User>. The type parameter describes your application data; it does not validate the response at runtime.
tsimport ky from 'ky';
type User = {
name: string;
};
async function main() {
const user = await ky.get('https://api.example.com/users/1').json<User>();
console.log(user.name);
}
main().catch(console.error);
TypeScript lets you access user.name as a string. You can also put the type parameter on the request, as in ky.get<User>(url).json(). Use a schema instead when you need to check what the server actually sends.
3. Validate the response with a schema
Pass a Standard Schema compatible validator, such as Zod 3.24+, to .json(schema) to get the schema's validated output; see Handle request errors for handling SchemaValidationError.
Install Zod for this example:
bashnpm install zod
tsimport ky, {SchemaValidationError} from 'ky';
import {z} from 'zod';
const userSchema = z.object({name: z.string()});
async function main() {
try {
const result = await ky.get('https://api.example.com/users/1').json(userSchema);
const user = userSchema.parse(result);
console.log(user.name);
} catch (error) {
if (error instanceof SchemaValidationError) {
console.error(error.issues);
} else {
throw error;
}
}
}
main().catch(console.error);
With a valid response, user.name is a string inferred from the schema. With an invalid response, the catch block logs the issues instead. For handling request failures alongside schema rejection, see Handle request errors.
4. Customize serialization and parsing
Use stringifyJson to change the outgoing representation with a replacer. This example omits internalNote from the serialized body while retaining name:
tsimport ky from 'ky';
async function main() {
const result = await ky.post('https://api.example.com/users', {
json: {name: 'Ada', internalNote: 'Local draft'},
stringifyJson: data => {
const text = JSON.stringify(data, (key, value: unknown) =>
key === 'internalNote' ? undefined : value,
);
if (text === undefined) {
throw new TypeError('The request value cannot be serialized to JSON');
}
return text;
},
}).json();
console.log(result);
}
main().catch(console.error);
Use parseJson to change how Ky consumes response text. It receives the text and a context object containing request and response. Calling .json() without a schema on an empty response body throws because the body cannot be parsed. Provide custom empty-body handling with parseJson:
tsimport ky from 'ky';
async function main() {
const result = await ky.get('https://api.example.com/users/1', {
parseJson: (text, {request, response}) => {
console.log(`Parsing JSON from ${request.url} (status: ${response.status})`);
if (text.trim() === '') {
return null;
}
const parsed: unknown = JSON.parse(text);
return parsed;
},
}).json();
console.log(result);
}
main().catch(console.error);
This returns null for an empty or whitespace-only body and parsed JSON otherwise. Inspect the URL and status in your own request's log. When you also pass a schema to .json(schema), Ky runs parseJson before validation, so the schema must accept the value your parser returns.
Options that matter
| Option | Type | Default | What it does |
|---|---|---|---|
json | unknown | Not set | Serializes an outgoing value into the request body. Use a value accepted by JSON.stringify(). |
stringifyJson | (data: unknown) => string | JSON.stringify() | Replaces outgoing JSON serialization. |
parseJson | (text: string, context: {request: Request; response: Response}) => unknown | JSON.parse() | Replaces incoming JSON parsing, including custom empty-body handling. |
For JSON content-type headers, see the quick start.
Related
- Ky quick start — Make your first request.
- Instances and defaults — Reuse request options across calls.
- Handle request errors — Handle HTTP, network, timeout, and schema errors.
Was this page helpful?