# Defining Errors

`Schema.TaggedErrorClass` defines a typed error that is, all at once: a
**schema** (so it can be validated, serialized, and sent across a boundary), a
**tagged** type (so it pattern-matches by `_tag`), and a **yieldable** Effect
error (so you can `yield*` it directly inside `Effect.gen`). It is the standard
way to model failures in Effect v4.

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

// `class Self extends Schema.TaggedErrorClass<Self>()("Tag", { fields }) {}`
// The empty () precedes the (tag, fields) call.
class UserNotFound extends Schema.TaggedErrorClass<UserNotFound>()(
  "UserNotFound",
  { id: Schema.String }
) {}

const findUser = Effect.fn("findUser")(function*(id: string) {
  if (id === "missing") {
    // The error instance is yieldable — fail the Effect by yielding it.
    return yield* new UserNotFound({ id })
  }
  return { id, name: "Alice" }
})
```

Here `findUser` has type `Effect<{ id: string; name: string }, UserNotFound>` —
the error is tracked in the typed error channel, exactly like any other Effect
error covered in [error management](https://effect.plants.sh/error-management/).

## Why schema-backed errors

Defining errors as schemas (rather than plain classes) buys you:

- **A `_tag` for matching** — `Effect.catchTag` and `Effect.catchTags` route on
  the tag with full type narrowing.
- **Serializability** — the error has an `Encoded` form, so it survives the trip
  across [RPC](https://effect.plants.sh/rpc/), [Cluster](https://effect.plants.sh/cluster/), and [HTTP API](https://effect.plants.sh/http-api/)
  boundaries and is reconstructed with the right type on the other side.
- **Validated, structured fields** — the payload is described by a schema, so
  fields are typed and can themselves be validated.

## Handling tagged errors

Because each error carries its `_tag`, you recover from it with the tag-based
combinators. `Effect.catchTag` handles one tag; `Effect.catchTags` handles
several; `Effect.catch` is the catch-all.

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

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

class ReservedPortError extends Schema.TaggedErrorClass<ReservedPortError>()(
  "ReservedPortError",
  { port: Schema.Number }
) {}

declare const loadPort: (
  input: string
) => Effect.Effect<number, ParseError | ReservedPortError>

const recovered = loadPort("80").pipe(
  // Handle several tags at once; fall back to a default port.
  Effect.catchTag(["ParseError", "ReservedPortError"], () => Effect.succeed(3000))
)

const withFallback = loadPort("invalid").pipe(
  // Handle one tag specifically...
  Effect.catchTag("ReservedPortError", () => Effect.succeed(3000)),
  // ...then catch anything else.
  Effect.catch(() => Effect.succeed(3000))
)
```

## Carrying an unknown cause

When an error wraps an underlying exception or unknown value, give it a `cause`
field typed as `Schema.Defect`. `Defect` accepts any value, so the original cause
is preserved through serialization without forcing you to model its shape.

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

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

const runQuery = Effect.fn("runQuery")(function*(sql: string) {
  return yield* Effect.tryPromise({
    try: () => Promise.resolve([] as Array<unknown>),
    // Wrap any thrown value in a typed, structured error.
    catch: (cause) => new DatabaseError({ query: sql, cause })
  })
})
```

## Tagged reasons

A single error often has multiple *reasons*. Rather than defining a separate
error per reason, give one error a `reason` field that is a union of tagged
reason errors. Effect provides `Effect.catchReason` / `Effect.catchReasons` to
match on the inner reason directly.

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

class RateLimitError extends Schema.TaggedErrorClass<RateLimitError>()(
  "RateLimitError",
  { retryAfter: Schema.Number }
) {}

class QuotaExceededError extends Schema.TaggedErrorClass<QuotaExceededError>()(
  "QuotaExceededError",
  { limit: Schema.Number }
) {}

class AiError extends Schema.TaggedErrorClass<AiError>()("AiError", {
  reason: Schema.Union([RateLimitError, QuotaExceededError])
}) {}

declare const callModel: Effect.Effect<string, AiError>

const handled = callModel.pipe(
  // Match on the parent tag and the specific reason tag.
  Effect.catchReason(
    "AiError",
    "RateLimitError",
    (reason) => Effect.succeed(`Retry after ${reason.retryAfter}s`),
    // Optional catch-all for the remaining reasons.
    (reason) => Effect.succeed(`Failed: ${reason._tag}`)
  )
)
```

See [reason errors](https://effect.plants.sh/error-management/reason-errors/) for the full reason-based
failure-modelling pattern and [tagged errors](https://effect.plants.sh/error-management/tagged-errors/)
for tag-based recovery in depth.
**Tip:** For non-error data classes — entities and value objects with methods and
value-based equality — use [`Schema.Class`](https://effect.plants.sh/schema/classes/) instead.
`Schema.TaggedErrorClass` is `Schema.Class` plus the yieldable error interface.

## Error class reference

The following section enumerates every error-related schema and helper. Each
entry has a short, runnable example. Decoding (`Schema.decodeUnknownSync`) goes
wire → in-memory; encoding (`Schema.encodeUnknownSync`) goes in-memory → wire.

### Schema.ErrorClass

A schema-backed `Error` subclass with validated, decodable/encodable fields and
the yieldable error interface — but **no** `_tag`. Use it when you want a
schema error without tagged-union semantics. Note the call shape: it takes the
identifier first, then the fields.

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

class NotFound extends Schema.ErrorClass<NotFound>("NotFound")({
  id: Schema.Number
}) {}

const err = new NotFound({ id: 1 })
err instanceof Error // => true
err.id // => 1

// It is a schema: decode an encoded payload back into an instance.
Schema.decodeUnknownSync(NotFound)({ id: 2 })
// => NotFound { id: 2 }
```

### Schema.TaggedErrorClass

The v4 idiom. Like `ErrorClass`, but automatically adds a `_tag` literal field
(filled from the tag, so you omit it in the constructor). Yieldable, matchable
by tag, and serializable. The leading `()` is where you can pass an optional
identifier; the tag itself is the first argument of the second call.

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

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

const program = Effect.gen(function*() {
  // Yield the instance to fail the Effect; _tag is auto-populated.
  yield* new NotFound({ id: 42 })
}).pipe(
  // Recover by matching the literal tag with full narrowing.
  Effect.catchTag("NotFound", (e) => Effect.succeed(`missing ${e.id}`))
)

new NotFound({ id: 1 })._tag // => "NotFound"
```

Throw it, yield it, or hand it to `Effect.fail` — and recover with
`Effect.catchTag` / `Effect.catchTags`:

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

class TooMany extends Schema.TaggedErrorClass<TooMany>()("TooMany", {
  count: Schema.Number
}) {}
class TooFew extends Schema.TaggedErrorClass<TooFew>()("TooFew", {
  count: Schema.Number
}) {}

declare const validate: Effect.Effect<void, TooMany | TooFew>

const safe = validate.pipe(
  Effect.catchTags({
    TooMany: (e) => Effect.logWarning(`too many: ${e.count}`),
    TooFew: (e) => Effect.logWarning(`too few: ${e.count}`)
  })
)
```

### Schema.Error

Schema for a plain JavaScript `Error`. Its default JSON serializer encodes an
`Error` as `{ message, name? }` (the **stack is omitted** for security) and
decodes that object back into an `Error`.

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

const json = Schema.encodeUnknownSync(Schema.Error)(new TypeError("boom"))
// => { name: "TypeError", message: "boom" }

const back = Schema.decodeUnknownSync(Schema.Error)({ name: "TypeError", message: "boom" })
// => TypeError: boom   (a real Error instance)
```

### Schema.ErrorWithStack

Identical to `Schema.Error`, but the JSON encoded form **preserves the stack**
(`{ message, name?, stack? }`). Use it when you control both ends and want stack
traces transmitted (e.g. trusted internal tooling).

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

const json = Schema.encodeUnknownSync(Schema.ErrorWithStack)(new Error("boom"))
// => { name: "Error", message: "boom", stack: "Error: boom\n    at ..." }
```

### Schema.Defect

A schema for **any** defect value: a JS `Error` (encoded as `{ message, name? }`,
no stack) or an arbitrary unknown value (round-tripped through `JSON.stringify`,
falling back to Effect's formatted representation). This is the field type to
use for an opaque `cause`.

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

Schema.encodeUnknownSync(Schema.Defect)(new Error("io"))
// => { name: "Error", message: "io" }

Schema.encodeUnknownSync(Schema.Defect)({ code: 42 })
// => { code: 42 }   (arbitrary unknown values pass through)
```

### Schema.DefectWithStack

Same as `Schema.Defect`, but `Error` defects keep their stack in the encoded
form (via `ErrorWithStack`). Arbitrary unknown defects behave as in `Defect`.

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

Schema.encodeUnknownSync(Schema.DefectWithStack)(new Error("io"))
// => { name: "Error", message: "io", stack: "Error: io\n    at ..." }
```
**Note:** `Schema.Error`, `Schema.ErrorWithStack`, `Schema.Defect`, and
`Schema.DefectWithStack` are powered internally by an `Error` ↔ `{ message,
name?, stack? }` JSON transformation; the `includeStack` toggle is what
distinguishes the stack-preserving variants. You consume the schemas directly —
there is no public transformation to call by hand.

## Tagging helpers

These low-level helpers underlie `TaggedErrorClass` and let you hand-build
tagged error payloads or reason unions.

### Schema.tag

A `Literal` schema with a constructor default, ideal for a discriminator field.
When constructing via `make`, the `_tag` may be omitted and is auto-filled.

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

const A = Schema.Struct({ _tag: Schema.tag("A"), value: Schema.Number })

A.make({ value: 42 })
// => { _tag: "A", value: 42 }   (_tag auto-filled)
```

### Schema.tagDefaultOmit

Like `tag`, but the discriminator is **omitted from the encoded output** while
still being filled on decode/construction. Useful when the wire format does not
carry the tag but you still want tagged-union matching in memory.

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

const A = Schema.Struct({
  _tag: Schema.tagDefaultOmit("A"),
  value: Schema.Number
})

Schema.encodeUnknownSync(A)({ _tag: "A", value: 1 })
// => { value: 1 }   (_tag stripped on encode)
```

### Schema.TaggedStruct

Shorthand for a struct with an auto-populated `_tag` field — the building block
of tagged error payloads and reason unions. `Schema.TaggedStruct("A", fields)`
is `Schema.Struct({ _tag: Schema.tag("A"), ...fields })`.

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

const Circle = Schema.TaggedStruct("Circle", { radius: Schema.Number })

Circle.make({ radius: 5 })
// => { _tag: "Circle", radius: 5 }
```

## Reason-style errors

A closed set of failure reasons is best modelled as a tagged union. These
helpers build and match such unions; pair them with a `reason` field on a
`TaggedErrorClass` (see [Tagged reasons](#tagged-reasons) above) and the
[reason errors](https://effect.plants.sh/error-management/reason-errors/) guide.

### Schema.TaggedUnion

Builds a discriminated union from a record of field sets, one per `_tag`. The
result carries `cases`, `guards`, `isAnyOf`, and `match` utilities for
pattern-matching the decoded value.

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

const Failure = Schema.TaggedUnion({
  RateLimit: { retryAfter: Schema.Number },
  Quota: { limit: Schema.Number }
})

const describe = Failure.match({ _tag: "RateLimit", retryAfter: 3 }, {
  RateLimit: (r) => `retry after ${r.retryAfter}s`,
  Quota: (q) => `quota ${q.limit}`
})
// => "retry after 3s"
```

### Schema.toTaggedUnion

Augments an **existing** `Schema.Union` of tagged structs with the same
`match` / `guards` / `isAnyOf` utilities, keyed by the chosen discriminant.

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

const A = Schema.TaggedStruct("A", { value: Schema.Number })
const B = Schema.TaggedStruct("B", { name: Schema.String })

const U = Schema.Union([A, B]).pipe(Schema.toTaggedUnion("_tag"))

U.guards.A({ _tag: "A", value: 1 }) // => true
U.isAnyOf(["A"])({ _tag: "B", name: "x" }) // => false
```

## Serializing causes, defects, exits, and results

Sending an Effect's outcome across the wire (RPC, Cluster) means encoding its
full failure context. These constructors take child schemas for the typed error
and the defect and produce schemas for the corresponding runtime structures.

### Schema.CauseReason

Schema for a single `Cause.Reason`: `Fail` uses the `error` schema, `Die` uses
the `defect` schema, and `Interrupt` carries only an optional fiber id.

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

const ReasonSchema = Schema.CauseReason(Schema.String, Schema.Defect)

Schema.encodeUnknownSync(ReasonSchema)(Cause.makeFailReason("boom"))
// => { _tag: "Fail", error: "boom" }

Schema.encodeUnknownSync(ReasonSchema)(Cause.makeInterruptReason())
// => { _tag: "Interrupt", fiberId: undefined }
```

### Schema.Cause

Schema for a full `Cause<E>` — the ordered collection of reasons that caused a
failure. The `error` schema applies to `Fail` reasons, the `defect` schema to
`Die` reasons.

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

const CauseSchema = Schema.Cause(Schema.String, Schema.Defect)

const cause = Cause.fromReasons([
  Cause.makeFailReason("first"),
  Cause.makeFailReason("second")
])

Schema.encodeUnknownSync(CauseSchema)(cause)
// => [ { _tag: "Fail", error: "first" }, { _tag: "Fail", error: "second" } ]
```

### Schema.Exit

Schema for an `Exit<A, E>`: a `Success` carrying a value, or a `Failure`
carrying a serialized `Cause`. Takes value, error, and defect schemas. This is
how an effect's terminal outcome is transmitted over a boundary.

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

const ExitSchema = Schema.Exit(Schema.Number, Schema.String, Schema.Defect)

Schema.encodeUnknownSync(ExitSchema)(Exit.succeed(42))
// => { _tag: "Success", value: 42 }

Schema.encodeUnknownSync(ExitSchema)(Exit.fail("nope"))
// => { _tag: "Failure", cause: [ { _tag: "Fail", error: "nope" } ] }
```

### Schema.Result

Schema for a `Result<A, E>` (Effect's `Either` replacement): a `Success` value
or a `Failure` value. Lighter than `Exit` — it carries a plain error, not a
whole `Cause`.

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

const ResultSchema = Schema.Result(Schema.Number, Schema.String)

Schema.encodeUnknownSync(ResultSchema)(Result.succeed(1))
// => { _tag: "Success", success: 1 }

Schema.encodeUnknownSync(ResultSchema)(Result.fail("bad"))
// => { _tag: "Failure", failure: "bad" }

Schema.decodeUnknownSync(ResultSchema)({ _tag: "Failure", failure: "bad" })
// => Result.fail("bad")
```
**Tip:** `Schema.Exit` and `Schema.Result` are the wire formats behind
[RPC](https://effect.plants.sh/rpc/) and [Cluster](https://effect.plants.sh/cluster/) — a remote handler's typed failure and
defect channels are encoded with these so they reconstruct with the right
types on the caller's side.