Skip to content

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"

Each schema below decodes from and encodes to the same primitive type (its Type and Encoded coincide):

SchemaTypeScript type
Schema.Stringstring
Schema.Numbernumber
Schema.Booleanboolean
Schema.BigIntbigint
Schema.Symbolsymbol
Schema.Nullnull
Schema.Undefinedundefined
Schema.Voidvoid

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 integer

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.

SchemaAccepts
Schema.Unknownany value, typed as unknown
Schema.Anyany value, typed as any
Schema.Nevernothing (always fails)

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") -> throws

Schema.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.Type

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)) // 42

For 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 | null
const Bio = Schema.UndefinedOr(Schema.String) // string | undefined
const Avatar = Schema.NullishOr(Schema.String) // string | null | undefined

For discriminated unions keyed by a _tag field — the kind you pattern-match on — use Schema.TaggedUnion, covered in structs and records.

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: number
console.log(Schema.decodeUnknownSync(Schema.NumberFromString)("42")) // 42
// Encoded: string -> Type: Date
const date = Schema.decodeUnknownSync(Schema.DateFromString)(
"2026-05-30T00:00:00.000Z"
)
console.log(date instanceof Date) // true

Next, assemble these primitives into objects and collections with structs and records.