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.
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.
Why schema-backed errors
Section titled “Why schema-backed errors”Defining errors as schemas (rather than plain classes) buys you:
- A
_tagfor matching —Effect.catchTagandEffect.catchTagsroute on the tag with full type narrowing. - Serializability — the error has an
Encodedform, so it survives the trip across RPC, Cluster, and 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
Section titled “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.
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
Section titled “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.
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
Section titled “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.
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 for the full reason-based failure-modelling pattern and tagged errors for tag-based recovery in depth.
Error class reference
Section titled “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
Section titled “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.
import { Schema } from "effect"
class NotFound extends Schema.ErrorClass<NotFound>("NotFound")({ id: Schema.Number}) {}
const err = new NotFound({ id: 1 })err instanceof Error // => trueerr.id // => 1
// It is a schema: decode an encoded payload back into an instance.Schema.decodeUnknownSync(NotFound)({ id: 2 })// => NotFound { id: 2 }Schema.TaggedErrorClass
Section titled “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.
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:
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
Section titled “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.
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
Section titled “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).
import { Schema } from "effect"
const json = Schema.encodeUnknownSync(Schema.ErrorWithStack)(new Error("boom"))// => { name: "Error", message: "boom", stack: "Error: boom\n at ..." }Schema.Defect
Section titled “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.
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
Section titled “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.
import { Schema } from "effect"
Schema.encodeUnknownSync(Schema.DefectWithStack)(new Error("io"))// => { name: "Error", message: "io", stack: "Error: io\n at ..." }Tagging helpers
Section titled “Tagging helpers”These low-level helpers underlie TaggedErrorClass and let you hand-build
tagged error payloads or reason unions.
Schema.tag
Section titled “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.
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
Section titled “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.
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
Section titled “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 }).
import { Schema } from "effect"
const Circle = Schema.TaggedStruct("Circle", { radius: Schema.Number })
Circle.make({ radius: 5 })// => { _tag: "Circle", radius: 5 }Reason-style errors
Section titled “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 above) and the
reason errors guide.
Schema.TaggedUnion
Section titled “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.
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
Section titled “Schema.toTaggedUnion”Augments an existing Schema.Union of tagged structs with the same
match / guards / isAnyOf utilities, keyed by the chosen discriminant.
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 }) // => trueU.isAnyOf(["A"])({ _tag: "B", name: "x" }) // => falseSerializing causes, defects, exits, and results
Section titled “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
Section titled “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.
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
Section titled “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.
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
Section titled “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.
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
Section titled “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.
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")