---
title: Matching errors
description: Handle tagged-error unions exhaustively or partially with inferred handler return unions.
---

## Exhaustive instance matching

Every `TaggedError` instance has a `.match()` method:

```ts
const message = error.match({
  UserNotFound: (error) => `No user ${error.userId}`,
  PermissionDenied: (error) => `Missing permission ${error.permission}`,
});
```

Every `_tag` in the receiver union needs a handler. Handler parameters narrow to their exact error class. Different handler outputs produce a return union.

```ts
const outcome = error.match({
  UserNotFound: () => 404,
  PermissionDenied: () => "forbidden" as const,
});
// number | "forbidden"
```

This composes directly inside a `Result` error branch without annotating the inferred error:

```ts
const response = result.match({
  ok: (user) => ({ status: 200, body: user }),
  err: (error) =>
    error.match({
      UserNotFound: () => ({ status: 404, body: null }),
      DatabaseUnavailable: () => ({ status: 503, body: null }),
    }),
});
```

## Standalone `matchError`

Use `matchError(error, handlers)` when working with structurally tagged errors that were not created by `TaggedError`:

```ts
const message = matchError(error, {
  UserNotFound: (error) => `No user ${error.userId}`,
  PermissionDenied: (error) => `Missing permission ${error.permission}`,
});
```

The standalone function has the same exhaustive narrowing, return inference, and defect behavior as `.match()`. If the selected exhaustive handler throws, `.match()` and `matchError` throw `Panic` with the original exception as `cause`.

`match` is a reserved `TaggedError` instance name. The factory rejects payload property types named `match`, preventing constructor assignment from shadowing the method.

## Data-last matching

Annotate variant-specific parameters when the error union is not known until application:

```ts
const toMessage = matchError({
  UserNotFound: (error: UserNotFound) => `No user ${error.userId}`,
  PermissionDenied: (error: PermissionDenied) => error.message,
});

const message = toMessage(error);
```

## Partial matching with identity fallback

`matchErrorPartial` returns unhandled errors unchanged by default:

```ts
const transformed = matchErrorPartial(error, {
  UserNotFound: (error) => `No user ${error.userId}`,
});
// string | PermissionDenied
```

This makes incremental handling explicit instead of discarding the rest of the union.

## Custom fallback

```ts
const message = matchErrorPartial(
  error,
  { UserNotFound: (error) => `No user ${error.userId}` },
  (unhandled) => `Unexpected failure: ${unhandled.message}`,
);
```

## Recover selected variants

Pass `Result.err` as the fallback to preserve unhandled variants while recovering one:

```ts
const recovered = result.tryRecover(
  matchErrorPartial(
    {
      UserNotFound: (_error: UserNotFound) => Result.ok(guestUser),
    },
    Result.err,
  ),
);
```

## Match at boundaries

Keep domain functions returning typed errors. Match where the application can choose an HTTP status, CLI exit code, user message, retry policy, or compensating action.
