# Report errors from { data, error } clients

> captureResult() reports the errors Supabase, PostgREST and other clients return instead of throwing: one event typed by its Postgres code, grouped by type and operation, never the SQL.

Supabase, PostgREST and many other database clients do not throw: they return `{ data, error }`. Nothing reaches Belvador unless your code reports that `error` — so a missing permission or a cancelled query fails quietly for every user. `captureResult()` reports it in one line and hands the result back unchanged. Available from SDK 0.6.0 in every `@belvador/*` package.

## Use it

```ts
import { captureResult } from "@belvador/browser"; // every @belvador/* package exports it

const { data, error } = captureResult(
  await supabase.from("invoices").select("id, total").eq("customer_id", customerId),
  { operation: "select invoices" },
);
if (error) return showRetry(); // the result is unchanged; the error is already reported
```

`captureResult(result, { operation })` never throws and never changes the result: your existing `if (error)` code keeps working. When the call succeeded it does nothing.

`operation` names the call — `select invoices`, `rpc create_invoice` — never the SQL. It is cut to 100 characters on one line.

## What is sent

One error event per failing call:

| Field | Value |
|---|---|
| Error type and title | the type below, for example `db.permission_denied`, with `operation: message` |
| Message | the database's message, scrubbed like every other text and cut to 300 characters |
| Tags | `error.type`, `db.system.name` (`postgresql`), `db.operation.name`, `db.response.status_code` — names from OpenTelemetry's database conventions |
| Grouping | by type and operation: every permission error of `select invoices` is one issue, whatever the message says |

Never sent: the SQL, the error's `details` and `hint` (they can hold row values), and your `data`.

## Error types

| `error.type` | Codes | By default |
|---|---|---|
| `db.permission_denied` | `42501` | one event |
| `db.unique_violation` | `23505` | breadcrumb, no event |
| `db.foreign_key_violation` | `23503` | one event |
| `db.query_canceled` | `57014` | one event |
| `db.serialization_failure` | `40001`, `40P01` | one event |
| `db.no_rows` | `PGRST116` | breadcrumb, no event |
| `db.jwt_expired` | `PGRST301` | one event |
| `db.request_invalid` | `PGRST2xx` | one event |
| `db.error` | any other code | one event |

`db.unique_violation` and `db.no_rows` are usually normal flow — a duplicate sign-up, a lookup that found nothing — so by default they leave a breadcrumb that travels with the next real error instead of creating an event. To report every type for one call, pass your own list:

```ts
// Here a duplicate email is a bug, not normal flow: report every error type.
captureResult(await supabase.from("subscribers").insert({ email }), {
  operation: "insert subscribers",
  expected: [],
});
```

## On an official Sentry SDK

Servers on `@sentry/node`, `@sentry/bun` or `@sentry/deno` (Supabase Edge Functions too) bind the same `captureResult` to their own SDK with `@belvador/core/sentry` (from SDK 0.6.1):

```js title="db.mjs"
// captureResult on the app's own Sentry SDK (@sentry/node, @sentry/bun or @sentry/deno).
import * as Sentry from "@sentry/node";
import { captureResultWith } from "@belvador/core/sentry";

export const captureResult = captureResultWith(Sentry);
```

It uses your Sentry SDK's own functions, so it works whatever `@sentry/*` version you run; Belvador tests it with 11.9.0. Everything on this page applies unchanged. On Supabase Edge Functions the shared file already exports it — see [Supabase Edge Functions](https://belvador.dev/en/docs/sdk/supabase-edge).

## Good to know

- `captureResult` needs a started SDK (`init`, `withBelvador`, or `Sentry.init` on an official SDK). Without one it returns the result and reports `BLV_NOT_INITIALIZED` once — see [Verify the connection](https://belvador.dev/en/docs/sdk/verify).
- A thrown exception (a network failure inside the client, a bug in your code) is not a result: it is reported the usual way, by the SDK's error handlers or by `captureException`.
- Errors already reported by your own `captureException` are reported twice if you also wrap the result — pick one.
