> ## Documentation Index
> Fetch the complete documentation index at: https://confect.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Error Handling

> Declare typed errors on Confect functions and consume them at every call site.

A Confect function can declare a typed error in its spec. When the handler fails with a value matching that schema, the error travels across the function boundary and surfaces—already decoded—at every call site (React hooks, JS clients, and tests).

Typed errors are transported as Convex's native [`ConvexError`](https://docs.convex.dev/functions/error-handling/application-errors#throwing-application-errors), with the encoded error stored in `ConvexError.data`. The `returns` channel is left untouched, and non-Confect callers of the same Convex API see ordinary `ConvexError`s. For mutations, throwing a `ConvexError` triggers Convex's transaction rollback as usual.

Error schemas are optional. When omitted, the error type defaults to `never` and every call site keeps its prior shape.

## Declaring an error schema

Add an `error` field to any `FunctionSpec` constructor. The schema is an ordinary Effect `Schema`, so a `Schema.TaggedError` class works well for a single failure mode.

```ts confect/notes.spec.ts theme={null}
import { FunctionSpec, GroupSpec } from "@confect/core";
import * as Schema from "effect/Schema";

import { Id } from "./_generated/id";
import notes from "./_generated/tables/notes";

export class NoteNotFound extends Schema.TaggedError<NoteNotFound>()(
  "NoteNotFound",
  { noteId: Id("notes") },
) {}

export default GroupSpec.make().addFunction(
  FunctionSpec.publicQuery({
    name: "getOrFail",
    args: () => Schema.Struct({ noteId: Id("notes") }),
    returns: () => notes.Doc,
    error: () => NoteNotFound,
  }),
);
```

To declare more than one failure mode, combine them with `Schema.Union`.

```ts theme={null}
import * as Schema from "effect/Schema";

import { Id } from "./_generated/id";

export class NoteNotFound extends Schema.TaggedError<NoteNotFound>()(
  "NoteNotFound",
  { noteId: Id("notes") },
) {}

export class Forbidden extends Schema.TaggedError<Forbidden>()(
  "Forbidden",
  {},
) {}

FunctionSpec.publicMutation({
  name: "deleteOrFail",
  args: () => Schema.Struct({ noteId: Id("notes") }),
  returns: () => Schema.Null,
  error: () => Schema.Union(NoteNotFound, Forbidden),
});
```

## Failing from a handler

When a spec declares an `error` schema, the corresponding `FunctionImpl` handler's `Effect` error channel is typed as that schema's `Type`. Use `Effect.fail` (or `Effect.mapError`) to fail with any value matching the schema.

```ts confect/notes.impl.ts theme={null}
import { FunctionImpl } from "@confect/server";
import * as Effect from "effect/Effect";

import databaseSchema from "./_generated/schema";
import { DatabaseReader } from "./_generated/services";
import notes, { NoteNotFound } from "./notes.spec";

const getOrFail = FunctionImpl.make(
  databaseSchema,
  notes,
  "getOrFail",
  ({ noteId }) =>
    Effect.gen(function* () {
      const reader = yield* DatabaseReader;

      return yield* reader
        .table("notes")
        .get(noteId)
        .pipe(Effect.mapError(() => new NoteNotFound({ noteId })));
    }),
);
```

Without an `error` schema, the handler's error channel is `never` and any unmapped failure must still be cleared (typically with `Effect.orDie`). With one, declared failures travel through, and any other failure (a defect) still rejects/dies as before.

## Consuming typed errors

The decoded error is surfaced at every call site. The exact shape depends on the client.

### `@confect/react`

* **`useQuery`** returns a `QueryResult<A, E>`. Match it with `QueryResult.match`, where `onFailure` is required when the ref declares an `error` schema.
* **`useMutation`** and **`useAction`** return `Promise<Either<A, E>>` when the ref declares an `error` schema (and `Promise<A>` otherwise).

See [React](/clients/react) for full examples.

```tsx theme={null}
import { QueryResult, useQuery } from "@confect/react";
import refs from "../confect/_generated/refs";

const lookup = useQuery(refs.public.notes.getOrFail, { noteId });

QueryResult.match(lookup, {
  onLoading: (skipped) => (skipped ? null : "Looking up…"),
  onSuccess: (note) => `Found: ${note.text}`,
  onFailure: (error) => `Note ${error.noteId} not found.`,
});
```

### `@confect/js`

Both `HttpClient` and `WebSocketClient` add the decoded error to the returned `Effect`'s error channel, alongside the existing transport error and `ParseError`.

```ts theme={null}
HttpClient.query(refs.public.notes.getOrFail, { noteId });
// Effect.Effect<Note, NoteNotFound | HttpClientError | ParseError>
```

`WebSocketClient.reactiveQuery` carries typed errors too: when the subscribed query fails with a typed error, the returned `Stream` terminates with the decoded value in its error channel. See [HTTP](/clients/js/http) and [WebSocket](/clients/js/websocket).

### `@confect/test`

`TestConfect`'s `query`, `mutation`, and `action` decode typed errors into the `Effect` error channel, so test bridges see the same shape as the JS clients. See [Testing](/guides/testing).

## Non-typed failures

Failures that are not declared in the `error` schema are not silently swallowed. They propagate through Convex's normal failure path: they reject promises in `@convex/react`, fail `Effect`s with `HttpClientError`/`WebSocketClientError` in `@confect/js`, and surface as defects (cause) in `TestConfect`. Use a typed `error` schema for failures you want callers to handle, and let everything else die.
