Primitives
The leaves of every schema are primitives. Effect ships schemas for each JavaScript primitive, for individual literal values, and for the special top/bottom types. You combine these — and the structs in the next pages — into larger shapes.
import { Schema } from "effect"
// A single description reusing primitive and literal schemas.const Account = Schema.Struct({ id: Schema.String, balance: Schema.Number, active: Schema.Boolean, // A union of literals: only these three strings are accepted. tier: Schema.Literals(["free", "pro", "enterprise"])})
const account = Schema.decodeUnknownSync(Account)({ id: "acc_1", balance: 100, active: true, tier: "pro"})
console.log(account.tier) // "pro"Primitive schemas
Section titled “Primitive schemas”Each schema below decodes from and encodes to the same primitive type (its
Type and Encoded coincide):
| Schema | TypeScript type |
|---|---|
Schema.String | string |
Schema.Number | number |
Schema.Boolean | boolean |
Schema.BigInt | bigint |
Schema.Symbol | symbol |
Schema.Null | null |
Schema.Undefined | undefined |
Schema.Void | void |
For numbers, Schema.Finite rejects NaN/Infinity and Schema.Int requires
an integer — both are Schema.Number with a built-in filter
attached.
import { Schema } from "effect"
// Number that must be a finite integer.const Count = Schema.Int
console.log(Schema.decodeUnknownSync(Count)(3)) // 3// Schema.decodeUnknownSync(Count)(3.5) -> throws: not an integerTop and bottom types
Section titled “Top and bottom types”A few schemas describe the extreme ends of the type lattice. Use them
deliberately — Unknown and Any accept anything, so they opt out of
validation for that position.
| Schema | Accepts |
|---|---|
Schema.Unknown | any value, typed as unknown |
Schema.Any | any value, typed as any |
Schema.Never | nothing (always fails) |
Literals
Section titled “Literals”Schema.Literal matches one exact value. It accepts strings, numbers, bigints,
booleans, and null.
import { Schema } from "effect"
const Yes = Schema.Literal("yes")
console.log(Schema.decodeUnknownSync(Yes)("yes")) // "yes"// Schema.decodeUnknownSync(Yes)("no") -> throwsSchema.Literals is the shorthand for a union of literal values — the idiomatic
way to model a closed set of string constants (a “string enum”):
import { Schema } from "effect"
const Status = Schema.Literals(["active", "inactive", "pending"])
// Type: "active" | "inactive" | "pending"type Status = typeof Status.TypeUnions
Section titled “Unions”Schema.Union accepts a value matching any of its members. Members are tried in
order and the first match wins.
import { Schema } from "effect"
// Accepts a string OR a number.const StringOrNumber = Schema.Union([Schema.String, Schema.Number])
console.log(Schema.decodeUnknownSync(StringOrNumber)("hello")) // "hello"console.log(Schema.decodeUnknownSync(StringOrNumber)(42)) // 42For modelling “this value, or null/undefined”, reach for the dedicated
combinators rather than spelling out the union by hand:
import { Schema } from "effect"
const Nickname = Schema.NullOr(Schema.String) // string | nullconst Bio = Schema.UndefinedOr(Schema.String) // string | undefinedconst Avatar = Schema.NullishOr(Schema.String) // string | null | undefinedFor discriminated unions keyed by a
_tagfield — the kind you pattern-match on — useSchema.TaggedUnion, covered in structs and records.
Built-in transforming primitives
Section titled “Built-in transforming primitives”Some primitive-shaped schemas decode into a different type than they encode from. These are full transformations, but they are common enough to know up front:
import { Schema } from "effect"
// Encoded: string -> Type: numberconsole.log(Schema.decodeUnknownSync(Schema.NumberFromString)("42")) // 42
// Encoded: string -> Type: Dateconst date = Schema.decodeUnknownSync(Schema.DateFromString)( "2026-05-30T00:00:00.000Z")console.log(date instanceof Date) // trueNext, assemble these primitives into objects and collections with structs and records.