# Tagged Errors

Expected errors are just values, so you need a way to *define* them. In Effect
v4 the idiomatic tool is `Schema.TaggedErrorClass`: it produces an error class
that carries a string `_tag` discriminator, validates its fields with
[Schema](https://effect.plants.sh/schema/), and can be `yield*`-ed directly inside `Effect.gen`.

```ts
import { Effect, Schema } from "effect"

// Each error is a class with a unique `_tag` and a set of schema-typed fields.
class UserNotFound extends Schema.TaggedErrorClass<UserNotFound>()(
  "UserNotFound",
  { id: Schema.Number }
) {}

class Unauthorized extends Schema.TaggedErrorClass<Unauthorized>()(
  "Unauthorized",
  { action: Schema.String }
) {}

// A function that returns an Effect should be written with `Effect.fn`, not as
// a plain function that returns `Effect.gen(...)`.
const loadProfile = Effect.fn("loadProfile")(function* (id: number, admin: boolean) {
  if (id <= 0) {
    // Yielding an error fails the effect with that error in the E channel.
    return yield* new UserNotFound({ id })
  }
  if (!admin) {
    return yield* new Unauthorized({ action: "read-profile" })
  }
  return { id, name: "Ada Lovelace" }
})

//      ┌─── the union of every error the function can produce
//      ▼
// Effect<{ id: number; name: string }, UserNotFound | Unauthorized>
const program = loadProfile(42, true)
```

## Anatomy of a tagged error

`Schema.TaggedErrorClass<Self>()("Tag", fields)` returns a class. Note the empty
`()` after the type argument — that two-step call is what lets TypeScript infer
the field types precisely.

- The first string, `"UserNotFound"`, becomes the value of the readonly `_tag`
  property. That tag is the discriminator used by `Effect.catchTag`,
  `Effect.catchTags`, and `Effect.match`.
- The fields object describes the error's payload using Schema. Here `id` must
  be a `number`; passing anything else is a type error at the construction site.
- The resulting class is *yieldable*: `yield* new UserNotFound({ id })` inside
  `Effect.gen` fails the effect with that error, no `Effect.fail` wrapper needed.

You can still use `Effect.fail` when composing with `.pipe`:

```ts
import { Effect, Schema } from "effect"

class ParseError extends Schema.TaggedErrorClass<ParseError>()("ParseError", {
  input: Schema.String,
  message: Schema.String
}) {}

const parsePort = (input: string) => {
  const port = Number(input)
  return Number.isNaN(port)
    ? Effect.fail(new ParseError({ input, message: "not a number" }))
    : Effect.succeed(port)
}
```

## Why tagged?

The `_tag` field turns your errors into a *discriminated union*, exactly like the
tagged unions you would design by hand. This is what makes recovery type-safe:
when you handle `"UserNotFound"`, TypeScript narrows the value to the
`UserNotFound` class (so `error.id` is available) and *removes* it from the
remaining error channel.

```ts
import { Effect, Schema } from "effect"

class UserNotFound extends Schema.TaggedErrorClass<UserNotFound>()(
  "UserNotFound",
  { id: Schema.Number }
) {}

declare const program: Effect.Effect<string, UserNotFound>

const handled = program.pipe(
  // `error` is narrowed to UserNotFound here, so `error.id` is a number.
  Effect.catchTag("UserNotFound", (error) =>
    Effect.succeed(`No user with id ${error.id}`)
  )
)
// handled: Effect<string, never> — the error has been eliminated from the type
```

## Carrying an unknown cause

Real errors often wrap an underlying failure. Use `Schema.Defect` for a field
that may hold *any* value (a caught exception, a driver error, anything),
preserving it without forcing it into a specific shape.

```ts
import { Effect, Schema } from "effect"

class DatabaseError extends Schema.TaggedErrorClass<DatabaseError>()(
  "DatabaseError",
  {
    query: Schema.String,
    // `Schema.Defect` accepts an unknown underlying cause.
    cause: Schema.Defect
  }
) {}

const runQuery = (query: string) =>
  Effect.try({
    try: (): Array<unknown> => {
      throw new Error("connection refused")
    },
    // Capture whatever was thrown as the error's `cause`.
    catch: (cause) => new DatabaseError({ query, cause })
  })
```
**Note:** Because tagged errors are schema-backed, they serialize and deserialize
  cleanly — useful when errors cross a boundary such as
  [RPC](https://effect.plants.sh/rpc/) or [HTTP API](https://effect.plants.sh/http-api/). The same class that fails an effect on
  the server can be reconstructed on the client.

## Next steps

- Recover from these errors by tag with
  [Catching Errors](https://effect.plants.sh/error-management/catching-errors/).
- Model a fixed set of *sub*-causes inside one error with
  [Reason Errors](https://effect.plants.sh/error-management/reason-errors/).