# Errors (/docs/client/sdk/errors)

---
title: Errors
description: What a failed call throws, what it carries, and how to pull a per-field message out of it.
kind: guide
package: questpie
---

A form rejected your input. Which field, and what did it say? The client hands
you that without you parsing anything.

## The one you will catch

Every method that goes through `fetch` throws `QuestpieClientError` on a
non-2xx response. That is collections, globals, routes and search. Uploads are
the exception and throw `UploadError` instead.

```ts
import { QuestpieClientError } from "questpie/client";

try {
	// `title` was declared as f.text(255).
	await client.collections.posts.create({ title: tooLong, slug: "hello" });
} catch (err) {
	if (err instanceof QuestpieClientError) {
		err.status; // 400
		err.isCode("VALIDATION_ERROR"); // true
		err.getFieldError("title"); // { path: "title", message: "…" }
		err.getFieldErrorsMap(); // { title: ["…"] }
	}
}
```

TypeScript already caught the missing keys and the wrong types. What is left
here is what only the server knows: a rule your schema declared, a unique
column, an access decision.

## What it carries

| Property      | What it holds                                                     |
| ------------- | ----------------------------------------------------------------- |
| `status`      | The HTTP status                                                   |
| `statusText`  | The HTTP status text                                              |
| `url`         | The full URL that was called                                      |
| `message`     | The server's message, or `Request failed:` and the status text    |
| `code`        | The app's own error code                                          |
| `fieldErrors` | An array of `{ path, message }`, plus translation keys            |
| `context`     | Extra detail the server attached, such as which access rule fired |

The first four are always there. `code`, `fieldErrors` and `context` need the
body to have been the app's own error envelope. A proxy returning its own HTML
leaves all three undefined.

## Reading field errors

Three ways in, depending on what your form wants.

```ts
err.fieldErrors; // the raw array
err.getFieldError("email"); // the first entry for one path, or undefined
err.getFieldErrorsMap(); // { email: ["Invalid format"], password: ["Too short"] }
```

`path` is dotted for nested values, so a bad title on the second nested post is
`posts.1.title`. `getFieldErrorsMap()` groups by path, which is usually what a
form wants. It returns `{}` when there were no field errors.

## The codes

`isCode(code)` compares against these. Its argument is typed to the union
below, so a misspelt code is a compile error rather than a silent `false`.

| Code                                  | Status |
| ------------------------------------- | ------ |
| `BAD_REQUEST`, `VALIDATION_ERROR`     | 400    |
| `UNAUTHORIZED`                        | 401    |
| `FORBIDDEN`                           | 403    |
| `NOT_FOUND`                           | 404    |
| `CONFLICT`                            | 409    |
| `PRECONDITION_FAILED`                 | 412    |
| `UNPROCESSABLE_CONTENT`               | 422    |
| `INTERNAL_SERVER_ERROR`, `HOOK_ERROR` | 500    |
| `NOT_IMPLEMENTED`                     | 501    |

`VALIDATION_ERROR` is the usual source of `fieldErrors`, but not the only one.
A `BAD_REQUEST` can carry them, and a unique-constraint clash arrives as a
`CONFLICT` with the offending column in `fieldErrors`. Branch on `fieldErrors`
rather than the code. `CONFLICT` also covers a stale `expectedRevision`, and
`NOT_IMPLEMENTED` is what a feature the collection never enabled returns.

## The other error types

| Type                               | Thrown by                                |
| ---------------------------------- | ---------------------------------------- |
| `UploadError`                      | `upload` and `uploadMany`                |
| `RealtimeTopicRejectedError`       | A subscription the server refused        |
| `RealtimeCrdtBindingRejectedError` | A collaborative document binding refused |

All of them are value exports from `questpie/client`, so `instanceof` works.

## Next

**[Framework adapters](/docs/client/sdk/framework-adapters)** covers mounting
the handler these calls reach.
