# Resolve request URLs

Use [`ky`](https://bench-ky-61.atloria.app/p/bench-ky-61-6TqdzXtmKC/developer/ky#default) to resolve endpoint paths against an API base, prepend a path when leading slashes must append, and merge query parameters into the outgoing URL. Ky builds on Fetch: you pass a string, `URL`, or `Request` and receive a response.

The samples run in a browser or Node.js with native Fetch support. They use absolute API addresses so URL construction also works on the server. Replace `https://api.example.com` with your API's origin; inspect the outgoing URLs in the console rather than relying on the service to echo them.

## 1. Resolve paths with `baseUrl`

Use `baseUrl` for standard URL resolution. Give a base path a trailing slash and pass a page-relative input such as `'users'` to extend that path. Create an instance with the base as a default, then make requests through it:

```ts
import ky from 'ky';

const api = ky.create({
	baseUrl: 'https://api.example.com/v1/',
	hooks: {
		beforeRequest: [({request}) => {
			console.log('Outgoing URL:', request.url);
		}],
	},
});

async function main() {
	await api.get('users');
	await api.get('/users');
}

main().catch(console.error);
```

The first call requests `/v1/users`; the second requests `/users` at the same origin. Each awaited call returns a response. The hook logs the constructed request URL before Ky sends it.

The input's leading slash means origin-root, not “append to the base.” A base without a trailing slash also changes how page-relative inputs resolve:

| `baseUrl` | Input | Destination |
| --- | --- | --- |
| `https://api.example.com/v1/` | `users` | `https://api.example.com/v1/users` |
| `https://api.example.com/v1/` | `/users` | `https://api.example.com/users` |
| `https://api.example.com/v1` | `users` | `https://api.example.com/users` |

An absolute input bypasses `baseUrl`. For example, with no `prefix`, `'https://other.example.com/users'` stays at that address.

## 2. Append leading-slash inputs with `prefix`

Prefer `baseUrl` in most cases because it follows web standards. Use `prefix` when inputs such as `/users` must append to an API path instead of discarding it.

Ky joins `prefix` and the string input first, trimming trailing slashes from the prefix and leading slashes from the input at the join boundary. It then resolves the joined result against `baseUrl`, if present.

```ts
import ky from 'ky';

const api = ky.create({
	baseUrl: 'https://api.example.com/',
	prefix: 'v1',
	hooks: {
		beforeRequest: [({request}) => {
			console.log('Outgoing URL:', request.url);
		}],
	},
});

async function main() {
	await api.get('users');
	await api.get('/users');
}

main().catch(console.error);
```

Both calls request `https://api.example.com/v1/users`. A trailing slash on `prefix` is optional. Unlike `baseUrl`, `prefix` does string joining even when the string input contains an absolute URL; do not use it as an absolute-URL fallback.

## 3. Merge query parameters, including on a `Request`

Pass `searchParams` to add query parameters without building a query string yourself. Existing input parameters remain, and new values append rather than replace existing values with the same name. Use an object value of `undefined` to delete a parameter.

A `Request` input ignores `baseUrl` and `prefix`, but `searchParams` still applies:

```ts
import ky from 'ky';

const request = new Request(
	'https://api.example.com/users?role=reader&debug=true',
);

async function main() {
	await ky.get(request, {
		baseUrl: 'https://other.example.com/',
		prefix: 'v1',
		searchParams: {
			page: 2,
			active: true,
			debug: undefined,
		},
		hooks: {
			beforeRequest: [({request: outgoingRequest}) => {
				console.log('Outgoing URL:', outgoingRequest.url);
			}],
		},
	});
}

main().catch(console.error);
```

The outgoing URL stays at `https://api.example.com/users`. It retains `role=reader`, adds `page=2` and `active=true`, and removes `debug`. The request does not move to `other.example.com` or acquire a `/v1/` path.

You can also pass a query string, a `URLSearchParams` instance, or an array of key-value pairs. For example, repeated keys represent multiple values; adding another `role` does not erase the input's `role=reader`.

## Options that matter

These fields belong to [`Options`](https://bench-ky-61.atloria.app/p/bench-ky-61-6TqdzXtmKC/developer/ky-options#options).

| Option | Type | Default | What it does |
| --- | --- | --- | --- |
| `baseUrl` | `string \| URL` | `undefined` | Resolves relative string inputs after prefix joining; absolute inputs bypass it. |
| `prefix` | `string \| URL` | `''` | Prepends to string inputs, normalizing slashes at the join boundary. |
| `searchParams` | Query string, object, key-value pairs, or `URLSearchParams` | `''` | Merges query parameters into the input URL; object values of `undefined` delete keys. |

## Server-side and input pitfalls

- Server-side requests require an absolute URL. Use an absolute `baseUrl`, as above, rather than a browser-relative base such as `/api/`. In browsers, a relative base resolves against the environment's base URL, such as `document.baseURI`.
- To extend a base path, keep its trailing slash and omit the input's leading slash. Use `prefix` instead when leading-slash inputs must append.
- `prefix` only affects string inputs. Construct a `URL` or `Request` with its intended destination before passing it to Ky; neither receives prefix joining, and a `Request` also ignores `baseUrl`.

## Related

- [Instances and defaults](https://bench-ky-61.atloria.app/p/bench-ky-61-6TqdzXtmKC/developer/instances-and-defaults) — share URL options across requests.
- [Request lifecycle](https://bench-ky-61.atloria.app/p/bench-ky-61-6TqdzXtmKC/developer/request-lifecycle) — inspect or modify requests with hooks.
- [Handle request errors](https://bench-ky-61.atloria.app/p/bench-ky-61-6TqdzXtmKC/developer/handle-request-errors) — handle failures after constructing the URL.
