# Classes & Opaque Types

`Schema.Class` turns a struct schema into a *class*. You get validated
construction, a named type you can attach methods to, and — because the class
extends Effect's `Data` — value-based equality out of the box. It is the
idiomatic way to model a domain entity that is more than a bag of fields.

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

// `class Self extends Schema.Class<Self>("Identifier")({ fields }) {}`
class Person extends Schema.Class<Person>("Person")({
  firstName: Schema.String.check(Schema.isNonEmpty()),
  lastName: Schema.String.check(Schema.isNonEmpty()),
  age: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))
}) {
  // Regular methods and getters work as you would expect.
  get fullName(): string {
    return `${this.firstName} ${this.lastName}`
  }
}

// The constructor validates its input against the schema.
const alice = new Person({ firstName: "Alice", lastName: "Ng", age: 30 })

console.log(alice.fullName) // => "Alice Ng"
console.log(`${alice}`) // => "Person({ firstName: Alice, lastName: Ng, age: 30 })"
```

## Why a class instead of a struct

A plain `Schema.Struct` produces anonymous objects. A `Schema.Class` gives you:

- **Validated construction** — `new Person({...})` runs the schema's checks and
  throws on invalid input, so an instance is always valid.
- **Identity** — the type has a name (`Person`), useful in errors, logs, and
  pattern matching.
- **Behaviour** — methods and getters live alongside the data.
- **Value equality** — two instances with equal fields are `Equal.equals`.

The class *is* a schema, so you can decode and encode through it just like any
other schema.

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

class Person extends Schema.Class<Person>("Person")({
  name: Schema.String,
  age: Schema.Number
}) {}

// Decode unknown input straight into a Person instance.
const person = Schema.decodeUnknownSync(Person)({ name: "Bob", age: 25 })

console.log(person instanceof Person) // => true
```

## Value-based equality

Because the class extends `Data.Class`, instances compare by value rather than
by reference. This makes class instances safe to use as keys in a `HashMap`,
members of a `HashSet`, or anywhere Effect relies on structural equality (see
[Equal & Hash](https://effect.plants.sh/traits/equal-and-hash/)).

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

class Point extends Schema.Class<Point>("Point")({
  x: Schema.Number,
  y: Schema.Number
}) {}

const a = new Point({ x: 1, y: 2 })
const b = new Point({ x: 1, y: 2 })

console.log(Equal.equals(a, b)) // => true — same field values
console.log(a === b) // => false — different references
```

## Effectful construction

`new Person({...})` throws on invalid input, which is fine inside trusted code
where the inputs are statically known. When you would rather keep failures in
the [error channel](https://effect.plants.sh/error-management/), use the static `makeEffect` — it
returns an `Effect` that fails with a `SchemaError`.

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

class Person extends Schema.Class<Person>("Person")({
  name: Schema.String.check(Schema.isNonEmpty()),
  age: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))
}) {}

const program = Effect.gen(function*() {
  // Validation failures surface as a typed SchemaError, not a thrown exception.
  const person = yield* Person.makeEffect({ name: "Carol", age: 41 })
  yield* Effect.log(`Created ${person.name}`)
  return person
})
```

## Tagged classes

`Schema.TaggedClass` adds a `_tag` literal field automatically, so instances
participate in tagged-union pattern matching. Note the extra `()` — the `Self`
generic is supplied first, then the tag and fields are passed at the next call.

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

class Circle extends Schema.TaggedClass<Circle>()("Circle", {
  radius: Schema.Number
}) {}

const c = new Circle({ radius: 5 })

console.log(c._tag) // => "Circle"
console.log(c.radius) // => 5
```

The `_tag` is auto-populated as a constructor default, so you never pass it.
Combine several tagged classes into a `Schema.Union` and switch on `_tag`:

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

class Circle extends Schema.TaggedClass<Circle>()("Circle", {
  radius: Schema.Number
}) {}

class Square extends Schema.TaggedClass<Square>()("Square", {
  side: Schema.Number
}) {}

const Shape = Schema.Union([Circle, Square])

function area(shape: typeof Shape.Type): number {
  switch (shape._tag) {
    case "Circle":
      return Math.PI * shape.radius ** 2
    case "Square":
      return shape.side ** 2
  }
}

console.log(area(new Circle({ radius: 2 }))) // => 12.566...
console.log(area(new Square({ side: 3 }))) // => 9
```

## Extending a class

Every `Schema.Class` exposes a static `extend` to derive a subclass with extra
fields, inheriting the parent's fields, checks, and methods.

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

class Animal extends Schema.Class<Animal>("Animal")({
  name: Schema.String
}) {}

class Dog extends Animal.extend<Dog>("Dog")({
  breed: Schema.String
}) {}

const dog = new Dog({ name: "Rex", breed: "Labrador" })

console.log(dog.name) // => "Rex"
console.log(dog.breed) // => "Labrador"
```
**Tip:** For error types, use `Schema.TaggedErrorClass` (or `Schema.ErrorClass`) instead
— it is a class that also implements Effect's yieldable error interface. See
[defining errors](https://effect.plants.sh/schema/defining-errors/).

## API reference

### Schema.Class

`Schema.Class<Self>(identifier)(fieldsOrStruct, annotations?)` produces a class
whose constructor validates input and whose instances carry value identity. You
pass either a plain fields object or an existing `Schema.Struct`.

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

// From a fields object
class A extends Schema.Class<A>("A")({ n: Schema.Number }) {}

// From an existing Struct (reuse a struct schema you already have)
const Fields = Schema.Struct({ n: Schema.Number })
class B extends Schema.Class<B>("B")(Fields) {}

console.log(new A({ n: 1 }).n) // => 1
console.log(new B({ n: 2 }).n) // => 2
```

The class is a full schema — decode and encode through it directly:

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

class Person extends Schema.Class<Person>("Person")({
  name: Schema.String,
  age: Schema.Number
}) {}

const decode = Schema.decodeUnknownSync(Person)
console.log(decode({ name: "Bob", age: 25 }) instanceof Person) // => true

const encode = Schema.encodeUnknownSync(Person)
console.log(encode(new Person({ name: "Bob", age: 25 })))
// => { name: "Bob", age: 25 }
```

### new (constructor)

`new Self(props, options?)` validates `props` against the schema and throws on
failure. Pass `{ disableChecks: true }` in `options` to skip validation when you
trust the data.

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

class Age extends Schema.Class<Age>("Age")({
  value: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))
}) {}

console.log(new Age({ value: 30 }).value) // => 30

// Bypass validation for trusted data:
console.log(new Age({ value: -1 }, { disableChecks: true }).value) // => -1
```

### Self.make

Static `make(props, options?)` is the schema-style constructor, equivalent to
`new Self(...)`. It throws on invalid input and applies constructor defaults.

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

class Person extends Schema.Class<Person>("Person")({
  name: Schema.String
}) {}

console.log(Person.make({ name: "Alice" }).name) // => "Alice"
```

### Self.makeOption

Static `makeOption(props, options?)` returns an `Option<Self>` — `Some` on
success, `None` on validation failure — instead of throwing.

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

class Age extends Schema.Class<Age>("Age")({
  value: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))
}) {}

console.log(Option.isSome(Age.makeOption({ value: 5 }))) // => true
console.log(Age.makeOption({ value: -1 })) // => none()
```

### Self.makeEffect

Static `makeEffect(props, options?)` returns `Effect<Self, SchemaError>`,
moving validation failures into the error channel.

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

class Age extends Schema.Class<Age>("Age")({
  value: Schema.Number.check(Schema.isGreaterThanOrEqualTo(0))
}) {}

const program = Effect.gen(function*() {
  const age = yield* Age.makeEffect({ value: 5 })
  return age.value // => 5
})
```

### Self.fields

Static `fields` exposes the field schemas, so you can reuse or recompose them
when building other schemas.

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

class Person extends Schema.Class<Person>("Person")({
  name: Schema.String,
  age: Schema.Number
}) {}

// Reuse a subset of the class's fields in another struct.
const NameOnly = Schema.Struct({ name: Person.fields.name })

console.log(Object.keys(Person.fields)) // => ["name", "age"]
```

### Self.identifier

Static `identifier` returns the string identifier the class was created with.

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

class Person extends Schema.Class<Person>("Person")({ name: Schema.String }) {}

console.log(Person.identifier) // => "Person"
```

### Self.extend

Static `extend<Extended>(identifier)(newFields, annotations?)` derives a
subclass that inherits the parent's fields, checks, and methods, and adds the
new fields. New fields with the same key override the parent's.

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

class Animal extends Schema.Class<Animal>("Animal")({
  name: Schema.String
}) {
  greet() {
    return `Hi, I'm ${this.name}`
  }
}

class Dog extends Animal.extend<Dog>("Dog")({
  breed: Schema.String
}) {}

const dog = new Dog({ name: "Rex", breed: "Labrador" })
console.log(dog.greet()) // => "Hi, I'm Rex" (inherited method)
console.log(dog.breed) // => "Labrador"
```

### Self.mapFields

Static `mapFields(f, options?)` returns a new `Struct` (not a class) whose
fields are derived from the class's fields by `f`. Useful for composing a new
schema from an existing class's shape.

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

class Person extends Schema.Class<Person>("Person")({
  name: Schema.String,
  age: Schema.Number
}) {}

// Make every field optional in a derived struct. `Schema.optional` is a Struct
// lambda, so `Struct.map` applies it to each field.
const PartialPerson = Person.mapFields((fields) =>
  Struct.map(fields, Schema.optional)
)

console.log(Object.keys(PartialPerson.fields)) // => ["name", "age"]
```

### Self.annotate / annotateKey / check

The class inherits the standard schema combinators. `annotate` and `annotateKey`
attach annotations and return a rebuilt schema; `check` appends a refinement
that validates the constructed instance.

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

class Range extends Schema.Class<Range>("Range")({
  min: Schema.Number,
  max: Schema.Number
}) {}

// Add an instance-level invariant.
const ValidRange = Range.check(
  Schema.makeFilter((r) => (r.min <= r.max ? undefined : "min must be <= max"))
)

console.log(Schema.is(ValidRange)(new Range({ min: 1, max: 2 }))) // => true
console.log(Schema.is(ValidRange)(new Range({ min: 5, max: 2 }))) // => false
```
**Note:** `annotate`, `annotateKey`, and `check` return a *schema* (the rebuilt AST), not
a constructor. Use them where you need a schema value; keep `class ... extends`
for the named type and its instance API.

### Constructor defaults

Fields built with `Schema.withConstructorDefault` supply a value when omitted
from `make`/`new`. Defaults apply only during construction, never during
decoding or encoding.

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

class Config extends Schema.Class<Config>("Config")({
  retries: Schema.Number.pipe(
    Schema.optionalKey,
    Schema.withConstructorDefault(Effect.succeed(3))
  )
}) {}

console.log(new Config({}).retries) // => 3
console.log(new Config({ retries: 5 }).retries) // => 5
```

### Schema.TaggedClass

`Schema.TaggedClass<Self>()(tag, fields, annotations?)` is `Class` plus an
auto-populated `_tag` literal. The optional first call supplies `Self`; the
`tag` string both names the schema (by default) and becomes the `_tag` value.

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

class Loaded extends Schema.TaggedClass<Loaded>()("Loaded", {
  data: Schema.String
}) {}

const l = new Loaded({ data: "ok" })
console.log(l._tag) // => "Loaded"

// Decode preserves the tag.
console.log(Schema.decodeUnknownSync(Loaded)({ _tag: "Loaded", data: "ok" })._tag)
// => "Loaded"
```

### Schema.Opaque

`Schema.Opaque<Self>()(schema)` wraps a schema (typically a `Struct`) so its
decoded `Type` becomes the nominal type `Self`, without changing the runtime
shape. Unlike `Class`, instances are plain objects — there is no constructor,
no methods, and no `_tag`; you just get a named, opaque type alias backed by the
struct.

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

class User extends Schema.Opaque<User>()(
  Schema.Struct({
    id: Schema.Number,
    name: Schema.String
  })
) {}

// Decoded value is `User`, but at runtime it is a plain object.
const user = Schema.decodeUnknownSync(User)({ id: 1, name: "Alice" })
console.log(user) // => { id: 1, name: "Alice" }
console.log(user instanceof User) // => false (Opaque has no instances)
```

Because the opaque value's type *is* `Self`, you can refer to it by name and
read its fields directly off the schema (it inherits the struct's `fields`,
`make`, etc.):

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

class User extends Schema.Opaque<User>()(
  Schema.Struct({ id: Schema.Number, name: Schema.String })
) {}

// `make` comes from the underlying struct.
const u = User.make({ id: 2, name: "Bob" })
console.log(u.name) // => "Bob"

// Field schemas are accessible too.
console.log(Object.keys(User.fields)) // => ["id", "name"]
```
**Tip:** **`Opaque` vs `Class`.** Reach for `Opaque` when you want a *named, nominal
type* over a struct but still want plain objects (no class instances, cheap to
create, structural at runtime). Reach for `Class` when you want behaviour
(methods/getters), value-based `Equal`/`Hash`, and `instanceof` checks.

### Schema.asClass

`Schema.asClass(schema)` turns *any* schema (not just structs) into a class-like
constructor so you can `extends` it and attach static members that reference
`this`. The added construct signature is `new(_: never): {}` — it is not meant
to be instantiated; it exists so you can hang statics and a name on a schema.

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

class MyString extends Schema.asClass(Schema.String) {
  static readonly decodeUnknownSync = Schema.decodeUnknownSync(this)
}

console.log(MyString.decodeUnknownSync("a")) // => "a"
```
**Note:** **`asClass` vs `Class`.** Use `Class` to model a struct entity with validated
construction, instances, and equality. Use `asClass` only when you want to wrap
a non-struct schema (a primitive, a union, a transformation) in a named carrier
to which you can attach static helpers — there is no instance API, no `_tag`,
and no value equality.

### Schema.ErrorClass / Schema.TaggedErrorClass

`Schema.ErrorClass` and `Schema.TaggedErrorClass` build on `Class`
(`TaggedErrorClass` also adds a `_tag`) and additionally implement Effect's
yieldable error interface, so instances can be `yield*`-ed and matched with
`Effect.catchTag`. These are documented in full on the
[defining errors](https://effect.plants.sh/schema/defining-errors/) page.

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

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

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

## See also

- [Defining errors](https://effect.plants.sh/schema/defining-errors/) — `ErrorClass` and
  `TaggedErrorClass` for schema-backed, yieldable errors.
- [Data](https://effect.plants.sh/data-types/data/) — plain value classes (`Data.Class`,
  `Data.TaggedClass`) when you do not need a schema.
- [Equal & Hash](https://effect.plants.sh/traits/equal-and-hash/) — the structural-equality traits that
  `Schema.Class` instances participate in.