---
url: https://openapi-typesafe.github.io/openapi-typesafe/fetch.md
description: >-
  A type-safe fetch client for OpenAPI, with typed paths, parameters, bodies and
  responses and no generics
---

# openapi-typesafe-fetch

openapi-typesafe-fetch is a type-safe fetch client that pulls in your OpenAPI schema. It weighs about **9 kB** minified (3.5 kB gzipped), has virtually zero runtime, and works with React, Vue, Svelte, or vanilla JS. It is a drop-in successor to [openapi-fetch](https://github.com/openapi-ts/openapi-typescript/tree/main/packages/openapi-fetch) 0.17: in most projects you change only the import. The [migration guide](/migration-guide) lists the few differences.

The syntax is inspired by popular libraries like react-query or Apollo client, but without all the bells and whistles and in a small package.

::: code-group

```ts [src/my-project.ts]
import createClient from "openapi-typesafe-fetch";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typesafe

const client = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });

const {
  data, // only present if 2XX response
  error, // only present if 4XX or 5XX response
} = await client.GET("/blogposts/{post_id}", {
  params: {
    path: { post_id: "123" },
  },
});

await client.PUT("/blogposts", {
  body: {
    title: "My New Post",
  },
});
```

:::

`data` and `error` are typechecked and expose their shapes to Intellisense in VS Code (and any other IDE with TypeScript support). Likewise, the request `body` will also typecheck its fields, erring if any required params are missing, or if there’s a type mismatch.

`GET()`, `PUT()`, `POST()`, etc. are thin wrappers around the native [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API) (which you can [swap for any call](/fetch/api#createclient)).

Notice there are no generics, and no manual typing. Your endpoint’s request and response were inferred automatically. This is a huge improvement in the type safety of your endpoints because **every manual assertion could lead to a bug**! This eliminates all of the following:

* No typos in URLs or params
* All parameters, request bodies, and responses are type-checked against your schema
* No manual typing of your API
* Eliminates `any` types that hide bugs
* Also eliminates `as` type overrides that can also hide bugs
* All of this in a **9 kB** (minified) client package

## Setup

Install this library along with [openapi-typesafe](/introduction):

```bash
npm i openapi-typesafe-fetch
npm i -D openapi-typesafe typescript
```

::: tip Highly recommended

Enable [noUncheckedIndexedAccess](https://www.typescriptlang.org/tsconfig#noUncheckedIndexedAccess) in your `tsconfig.json` ([docs](/advanced#enable-nouncheckedindexedaccess-in-tsconfig))

:::

Next, generate TypeScript types from your OpenAPI schema using openapi-typesafe:

```bash
npx openapi-typesafe ./path/to/api/v1.yaml -o ./src/lib/api/v1.d.ts
```

::: tip Node.js without a bundler

Set `"module"` and `"moduleResolution"` to `"NodeNext"` in `tsconfig.json` and `"type": "module"` in
`package.json`, and import the types with the `.js` extension:
`import type { paths } from "./lib/api/v1.js"`. `"Bundler"` type-checks, but Node then fails to find
imports without an extension.

:::

Lastly, be sure to **run typechecking** in your project. This can be done by adding `tsc --noEmit` to your [npm scripts](https://docs.npmjs.com/cli/v9/using-npm/scripts) like so:

::: code-group

```json{3} [package.json]
{
  "scripts": {
    "test:ts": "tsc --noEmit"
  }
}
```

:::

And run `npm run test:ts` in your CI to catch type errors.

::: tip
Use `tsc --noEmit` to check for type errors rather than relying on your linter or your build command. Nothing will typecheck as accurately as the TypeScript compiler itself.
:::

## Basic usage

The best part about using openapi-typesafe-fetch over oldschool codegen is no documentation needed. openapi-typesafe-fetch encourages using your existing OpenAPI documentation rather than trying to find what function to import, or what parameters that function wants:

![OpenAPI schema example](/assets/openapi-schema.png)

::: code-group

```ts [src/my-project.ts]
import createClient from "openapi-typesafe-fetch";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typesafe

const client = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" });

const { data, error } = await client.GET("/blogposts/{post_id}", {
  params: {
    path: { post_id: "my-post" },
    query: { version: 2 },
  },
});

const { data: updated, error: updateError } = await client.PUT("/blogposts", {
  body: {
    title: "New Post",
    body: "<p>New post body</p>",
    publish_date: new Date("2023-03-01T12:00:00Z").getTime(),
  },
});
```

:::

1. You call the method (`GET()`, `PUT()`, …) on the client that `createClient()` returns
2. You pass in your desired `path` to `GET()`, `PUT()`, etc.
3. TypeScript takes over the rest and returns helpful errors for anything missing or invalid

### Pathname

The pathname of `GET()`, `PUT()`, `POST()`, etc. **must match your schema literally.** Note in the example, the URL is `/blogposts/{post_id}`. This library will quickly replace all `path` params for you (so they can be typechecked).

::: tip

openapi-typesafe-fetch infers types from the URL. Prefer static string values over dynamic runtime values, e.g.:

* Good: `"/blogposts/{post_id}"`
* Bad: `[...pathParts].join("/") + "{post_id}"`

:::

::: tip Reading type errors

A wrong path is reported on the arguments, not on the path string:

* A path that is not in the schema, such as `client.GET("/blogposts/5")` instead of
  `"/blogposts/{post_id}"`, gives `Expected 2 arguments, but got 1`. Pass the options object to see
  the real cause: `Argument of type '"/blogposts/5"' is not assignable to parameter of type
  'PathsWithMethod<paths, "get">'`.
* A path that exists but has no such method gives `not assignable to parameter of type 'never'`.

:::

This library also supports the **label** and **matrix** serialization styles as well ([docs](https://swagger.io/docs/specification/serialization/#path-parameters)) automatically.

### Request

The `GET()` request shown needed the `params` object that groups [parameters by type](https://spec.openapis.org/oas/latest.html#parameter-object) (`path` or `query`). If a required param is missing, or the wrong type, a type error will be thrown.

The `PUT()` request required a `body` object that provided all necessary [requestBody](https://spec.openapis.org/oas/latest.html#request-body-object) data.

### Response

All methods return an object with **data**, **error**, and **response**.

```ts
const { data, error, response } = await client.GET("/url");
```

| Object     | Response                                                                                                                    |
| :--------- | :-------------------------------------------------------------------------------------------------------------------------- |
| `data`     | `2xx` response if OK; otherwise `undefined`                                                                                 |
| `error`    | `5xx`, `4xx`, or `default` response if not OK; otherwise `undefined`                                                        |
| `response` | [The original Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) which contains `status`, `headers`, etc. |

`default` describes every status the operation does not declare. So when an operation declares no
`2xx` response, `data` has the type of `default` (and `error` too): a success can only be a
`default` one. With a `2xx` response declared, `default` types only `error`.

::: warning

A `4xx` / `5xx` response without a body has `error: undefined`, so `if (error)` alone does not catch it.
Check `response.ok` too:

```ts
const { data, error, response } = await client.GET("/url");
if (error !== undefined || !response.ok) {
  // handle the failure
}
```

:::

### Path-property style

If you prefer selecting the path as a property, you can create a path based client:

```ts
import { createPathBasedClient } from "openapi-typesafe-fetch";
import type { paths } from "./my-openapi-3-schema"; // generated by openapi-typesafe

const client = createPathBasedClient<paths>({
  baseUrl: "https://myapi.dev/v1",
});

client["/blogposts/{post_id}"].GET({
  params: {
    path: { post_id: "my-post" },
    query: { version: 2 },
  },
});
```

Note that this has performance implications and does not allow to attach middlewares directly.
See [`wrapAsPathBasedClient`](/fetch/api#wrapaspathbasedclient) for more.

### Large schemas

A schema with hundreds of paths works with both styles. Until the path you type is a known path,
TypeScript checks the call against every path of the method, so a wrong path is reported on the
path (`Argument of type '"/nope"' is not assignable…`) and the editor completes paths. Before
1.0, the init type of all those operations at once was too deep for TypeScript 5.4 to 6.0: a wrong
path gave `Type instantiation is excessively deep and possibly infinite` (TS2589) and no
completion (upstream #1882). The init is now computed per operation.

When every operation of the method requires an init (path or query parameters, or a body), a call
with a wrong path and no init reports the missing init first (`Expected 2 arguments`), as for any
overload TypeScript checks the argument count before the argument types.

With TypeScript 7, `Parameters<typeof client.request>` over hundreds of paths exceeds the
compiler's limit (TS2859, excessive complexity). Use the method of the call instead, as in
`Parameters<typeof client.GET>`.

### Libraries built on openapi-fetch

Packages such as [swr-openapi](https://github.com/htunnicliff/swr-openapi) import their client type
from `openapi-fetch`. They need this package under that name, so alias it:

::: code-group

```json [npm / bun]
{
  "overrides": {
    "openapi-fetch": "npm:openapi-typesafe-fetch@^1.0.0-rc"
  }
}
```

```yaml [pnpm-workspace.yaml]
overrides:
  openapi-fetch: npm:openapi-typesafe-fetch@^1.0.0-rc
```

```json [yarn]
{
  "resolutions": {
    "openapi-fetch": "npm:openapi-typesafe-fetch@^1.0.0-rc"
  }
}
```

:::

`^1.0.0-rc` matches the 1.0.0 release candidates as well as 1.0.0 and every later 1.x version; a
plain `^1.0.0` would leave out the release candidates.

Without the alias the package manager installs openapi-fetch for the library, and passing this
client to it fails with `Argument of type 'Client<paths>' is not assignable to parameter of type
'Client<{}>'` (TS2345). TypeScript infers the paths of a generic client type only from the same
declaration, so any client from another package fails there, openapi-fetch 0.15 against
swr-openapi's openapi-fetch 0.17 too. Types you write yourself in openapi-fetch's shape (a copied
`InitParam`, a path-based client type) need no alias.

## Support

| Platform       | Support                                                                                                                                            |
| :------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Browsers**   | [See fetch API support](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#browser_compatibility) (widely-available in all major browsers) |
| **Node**       | >= 22; Bun and Deno are tested too |
| **TypeScript** | >= 5.4, including 6.x and 7.x |
