# Error Handling

> 

A failed procedure call rejects with a [`TRPCClientError`](https://trpc.io/docs/client/vanilla/infer-types.md#infer-trpcclienterror-types). You can either let it throw and catch it, or use `safe()` to get it back as a type-safe value.

## Catching a thrown error

`query()` and `mutate()` behave like any other promise, so a `try`/`catch` works as you would expect. TypeScript has no way to describe what a function throws, though, so `cause` arrives as `unknown` and has to be narrowed:

```ts title="client.ts"
import { TRPCClientError } from '@trpc/client';
import type { AppRouter } from './server';
import { trpc } from './trpc';

try {
  const post = await trpc.post.byId.query('1');
} catch (cause) {
  if (cause instanceof TRPCClientError) {
    console.log(cause.data);
  } else {
    // something that isn't a tRPC error
  }
}
```

See [Inferring Types](https://trpc.io/docs/client/vanilla/infer-types.md#infer-trpcclienterror-types) for a reusable `isTRPCClientError` guard.

## Errors as values with `safe()`

`safe()` awaits a `query()` or `mutate()` call without letting it throw, handing back a `[data, error]` pair instead. Exactly one side is ever set, so checking either one narrows the other:

```ts title="client.ts"
import { safe } from '@trpc/client';
import { trpc } from './trpc';

const [post, error] = await safe(trpc.post.byId.query('1'));

if (error) {
  console.log(error.data);
  // (property) TRPCClientError<{ errorShape: FoldErrorShape<DefaultErrorShape, { data: { kind: "RATE_LIMIT"; retryAfterMs: number; code: TRPC_ERROR_CODE_KEY; httpStatus: number; path?: string; stack?: string; }; message: string; code: TRPC_ERROR_CODE_NUMBER; } | undefined>; transformer: boolean; }>.data: Maybe<DefaultErrorData | {
  //     kind: "RATE_LIMIT";
  //     retryAfterMs: number;
  //     code: TRPC_ERROR_CODE_KEY;
  //     httpStatus: number;
  //     path?: string;
  //     stack?: string;
  // }>
} else {
  console.log(post.title);
}
```

> [!NOTE] Info
> Subscriptions don't need `safe()` - they never throw at the call site, and report failures through `onError`, which is already typed with the procedure's error shape.
