Skip to main content

Schema

The Schema module provides a powerful way to define schemas that validate, parse, and transform data with compile-time type inference. Schemas bridge the gap between runtime and compile-time type safety.

Core Concepts

A schema describes:
  • The Type (T) - the validated/decoded output type
  • The Encoded form (E) - the input/encoded representation
  • Decoding - transformation from encoded to type
  • Encoding - transformation from type back to encoded
  • Services - dependencies required for validation

Type Signatures

Schema

interface Schema<out T> extends Top {
  readonly Type: T
}
T
type parameter
The decoded/output type of the schema

Codec

interface Codec<out T, out E = T, out RD = never, out RE = never> extends Schema<T> {
  readonly Encoded: E
  readonly DecodingServices: RD
  readonly EncodingServices: RE
}
T
type parameter
The decoded type (output)
E
type parameter
default:"T"
The encoded type (input)
RD
type parameter
default:"never"
Services required for decoding
RE
type parameter
default:"never"
Services required for encoding

Primitive Schemas

String

A schema for string values.
import { Schema } from "effect"

const Name = Schema.String
// Schema<string>
Signature:
const String: Schema<string>

Number

A schema for number values.
const Age = Schema.Number
// Schema<number>
Signature:
const Number: Schema<number>

Boolean

A schema for boolean values.
const IsActive = Schema.Boolean
// Schema<boolean>
Signature:
const Boolean: Schema<boolean>

Composite Schemas

Struct

Defines an object schema with typed fields.
const User = Schema.Struct({
  id: Schema.Number,
  name: Schema.String,
  email: Schema.String
})
// Schema<{ id: number; name: string; email: string }>
Signature:
function Struct<Fields extends Record<PropertyKey, Top>>(
  fields: Fields
): Codec</* inferred type */, /* inferred encoded */>
fields
Record<PropertyKey, Top>
required
Object mapping field names to their schemas
returns
Codec
A schema for objects with the specified fields

Array

Defines an array schema.
const Numbers = Schema.Array(Schema.Number)
// Schema<Array<number>>
Signature:
function Array<I, A>(
  item: Schema<A, I>
): Schema<Array<A>, Array<I>>
item
Schema<A, I>
required
Schema for array elements

Record

Defines a record/dictionary schema.
const StringToNumber = Schema.Record(
  Schema.String,
  Schema.Number
)
// Schema<Record<string, number>>
Signature:
function Record<K, V>(
  key: Schema<K>,
  value: Schema<V>
): Schema<Record<K, V>>

Union

Defines a union of schemas.
const StringOrNumber = Schema.Union(
  Schema.String,
  Schema.Number
)
// Schema<string | number>
Signature:
function Union<Members extends ReadonlyArray<Top>>(
  ...members: Members
): Codec</* union of types */, /* union of encoded */>

Tuple

Defines a tuple schema with fixed positions.
const Coordinate = Schema.Tuple(
  Schema.Number,
  Schema.Number
)
// Schema<[number, number]>
Signature:
function Tuple<Elements extends ReadonlyArray<Top>>(
  ...elements: Elements
): Codec</* tuple type */, /* tuple encoded */>

Refinements

filter

Adds a validation constraint to a schema.
const PositiveNumber = Schema.Number.pipe(
  Schema.filter((n) => n > 0, {
    message: () => "Must be positive"
  })
)
Signature:
function filter<S extends Top>(
  predicate: Predicate<Schema.Type<S>>,
  options?: {
    message?: (value: Schema.Type<S>) => string
  }
): (self: S) => S

brand

Creates a branded type for nominal typing.
const UserId = Schema.Number.pipe(
  Schema.brand("UserId")
)
// Schema<number & Brand<"UserId">>
Signature:
function brand<B extends string | symbol>(
  brand: B
): <S extends Top>(self: S) => /* branded schema */

Transformations

transform

Transforms between encoded and decoded representations.
const DateFromString = Schema.transform(
  Schema.String,
  Schema.Date,
  {
    decode: (s) => new Date(s),
    encode: (d) => d.toISOString()
  }
)
Signature:
function transform<From extends Top, To extends Top>(
  from: From,
  to: To,
  options: {
    decode: (encoded: Schema.Type<From>) => Schema.Type<To>
    encode: (type: Schema.Type<To>) => Schema.Type<From>
  }
): Codec<Schema.Type<To>, Schema.Type<From>>
from
Schema
required
The source schema (encoded form)
to
Schema
required
The target schema (decoded form)
options.decode
function
required
Function to transform from encoded to decoded
options.encode
function
required
Function to transform from decoded to encoded

Parsing and Validation

decode

Decodes/validates input according to a schema.
import { Effect, Schema } from "effect"

const User = Schema.Struct({
  name: Schema.String,
  age: Schema.Number
})

const program = Effect.gen(function*() {
  const result = yield* Schema.decode(User)({
    name: "Alice",
    age: 30
  })
  console.log(result) // { name: "Alice", age: 30 }
})
Signature:
function decode<I, A>(
  schema: Schema<A, I>
): (input: I, options?: ParseOptions) => Effect.Effect<A, SchemaError>
schema
Schema<A, I>
required
The schema to decode with
input
I
required
The input value to decode
options
ParseOptions
Optional parsing configuration
returns
Effect<A, SchemaError>
An effect that produces the decoded value or fails with SchemaError

encode

Encodes a value back to its encoded representation.
const encoded = yield* Schema.encode(DateFromString)(
  new Date("2024-01-01")
)
// "2024-01-01T00:00:00.000Z"
Signature:
function encode<I, A>(
  schema: Schema<A, I>
): (value: A, options?: ParseOptions) => Effect.Effect<I, SchemaError>

decodeSync

Synchronously decodes a value, throwing on error.
const user = Schema.decodeSync(User)({
  name: "Bob",
  age: 25
})
Signature:
function decodeSync<I, A>(
  schema: Schema<A, I>
): (input: I, options?: ParseOptions) => A
decodeSync throws if validation fails or if the schema requires async operations.

Annotations

annotate

Adds metadata annotations to a schema.
const Email = Schema.String.pipe(
  Schema.annotate({
    title: "Email Address",
    description: "A valid email address",
    examples: ["[email protected]"]
  })
)
Signature:
function annotate<S extends Top>(
  annotations: Annotations
): (self: S) => S
annotations
Annotations
required
Metadata to attach to the schema (title, description, examples, etc.)

Built-in Refinements

NonEmpty

A string that must not be empty.
const NonEmptyString = Schema.NonEmpty
// Schema<string> (validated non-empty)

Positive

A number that must be positive.
const Age = Schema.Positive
// Schema<number> (validated > 0)

Int

An integer number.
const Count = Schema.Int
// Schema<number> (validated integer)

Option and Result

Option

Schema for Option values.
const MaybeString = Schema.Option(Schema.String)
// Schema<Option<string>>
Signature:
function Option<I, A>(
  value: Schema<A, I>
): Schema<Option<A>, Option<I>>

Result

Schema for Result values.
const StringResult = Schema.Result({
  success: Schema.String,
  failure: Schema.Number
})
// Schema<Result<string, number>>
Signature:
function Result<S extends Top, F extends Top>(options: {
  success: S
  failure: F
}): Schema<Result<Schema.Type<S>, Schema.Type<F>>>

Type Extraction

Schema.Type

Extracts the decoded type from a schema.
const User = Schema.Struct({
  name: Schema.String,
  age: Schema.Number
})

type UserType = Schema.Type<typeof User>
// { name: string; age: number }

Codec.Encoded

Extracts the encoded type from a codec.
type Encoded = Schema.Codec.Encoded<typeof DateFromString>
// string

Complete Example

import { Effect, Schema } from "effect"

// Define a schema
const User = Schema.Struct({
  id: Schema.Number.pipe(Schema.brand("UserId")),
  name: Schema.NonEmpty,
  email: Schema.String.pipe(
    Schema.filter((s) => s.includes("@"), {
      message: () => "Must be a valid email"
    })
  ),
  age: Schema.Number.pipe(
    Schema.filter((n) => n >= 18, {
      message: () => "Must be 18 or older"
    })
  ),
  role: Schema.Union(
    Schema.Literal("admin"),
    Schema.Literal("user"),
    Schema.Literal("guest")
  )
})

// Extract the type
type User = Schema.Type<typeof User>

// Use the schema
const program = Effect.gen(function*() {
  // Decode/validate input
  const user = yield* Schema.decode(User)({
    id: 1,
    name: "Alice",
    email: "[email protected]",
    age: 30,
    role: "admin"
  })
  
  console.log(user)
  // {
  //   id: 1 & Brand<"UserId">,
  //   name: "Alice",
  //   email: "[email protected]",
  //   age: 30,
  //   role: "admin"
  // }
  
  return user
})

Effect.runPromise(program)
  .then(console.log)
  .catch(error => console.error("Validation failed:", error))

Build docs developers (and LLMs) love