Primitives, Literals & Unions
The leaves of every schema are primitives. Effect ships schemas for each
JavaScript primitive, for individual literal values, for common built-in classes
(Date, URL, RegExp, …), 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"Every schema on this page is non-transforming: its decoded Type equals its
encoded Encoded, so decoding and encoding pass values through unchanged (they
only validate). Schemas that convert between representations — NumberFromString,
DateFromString, the *FromString family, Option/Map/Set codecs — live in
transformations.
The common primitives
Section titled “The common primitives”These are the schemas you reach for constantly. Each decodes from and encodes to the same primitive type.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.String)("hi") // => "hi"Schema.decodeUnknownSync(Schema.Number)(42) // => 42Schema.decodeUnknownSync(Schema.Boolean)(true) // => trueSchema.decodeUnknownSync(Schema.BigInt)(10n) // => 10nFor “this value, or null/undefined”, combine a primitive with a dedicated
combinator instead of writing 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 a closed set of string constants (a “string enum”), use Schema.Literals:
import { Schema } from "effect"
const Status = Schema.Literals(["active", "inactive", "pending"])type Status = typeof Status.Type // "active" | "inactive" | "pending"The rest of this page is the exhaustive catalogue: every built-in primitive, literal/enum/template-literal constructor, and union combinator, each with a short runnable example.
Primitive schemas
Section titled “Primitive schemas”Each schema below decodes from and encodes to the matching JavaScript primitive.
Schema.String
Section titled “Schema.String”Accepts any string.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.String)("hello") // => "hello"Schema.Number
Section titled “Schema.Number”Accepts any number, including NaN, Infinity, and -Infinity. Use
Finite or Int to exclude those.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Number)(3.14) // => 3.14Schema.decodeUnknownSync(Schema.Number)(NaN) // => NaN (accepted)Schema.Boolean
Section titled “Schema.Boolean”Accepts true or false.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Boolean)(false) // => falseSchema.BigInt
Section titled “Schema.BigInt”Accepts any bigint. (The string codec BigIntFromString lives in
transformations.)
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.BigInt)(123n) // => 123nSchema.Symbol
Section titled “Schema.Symbol”Accepts any symbol. For one specific symbol, use
UniqueSymbol.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Symbol)(Symbol.for("x")) // => Symbol(x)Schema.Null
Section titled “Schema.Null”Accepts only null.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Null)(null) // => nullSchema.Undefined
Section titled “Schema.Undefined”Accepts only undefined.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Undefined)(undefined) // => undefinedSchema.Void
Section titled “Schema.Void”A schema whose Type is void. Useful for the return position of effects/RPCs
that produce no meaningful value.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Void)(undefined) // => undefinedSchema.ObjectKeyword
Section titled “Schema.ObjectKeyword”Accepts any non-null object (typed as object) — arrays and functions
included, but not primitives. This is the schema for the TypeScript object
keyword; for actual struct shapes use Schema.Struct (see
structs and records).
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.ObjectKeyword)({ a: 1 }) // => { a: 1 }Schema.decodeUnknownSync(Schema.ObjectKeyword)([1, 2]) // => [1, 2]// Schema.decodeUnknownSync(Schema.ObjectKeyword)("x") -> throwsSchema.PropertyKey
Section titled “Schema.PropertyKey”A union of the value types usable as object keys: finite number, symbol, or
string (Schema.Union([Schema.Finite, Schema.Symbol, Schema.String])).
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.PropertyKey)("key") // => "key"Schema.decodeUnknownSync(Schema.PropertyKey)(0) // => 0Top and bottom types
Section titled “Top and bottom types”These describe the extreme ends of the type lattice. Use Unknown/Any
deliberately — they opt that position out of validation.
Schema.Unknown
Section titled “Schema.Unknown”Accepts any value, typed as unknown. Validation defers to wherever the value is
later narrowed.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Unknown)({ anything: true }) // => { anything: true }Schema.Any
Section titled “Schema.Any”Accepts any value, typed as any. Same runtime behaviour as Unknown but with
TypeScript’s escape-hatch type — prefer Unknown.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Any)(42) // => 42Schema.Never
Section titled “Schema.Never”Accepts nothing — decoding always fails. Useful as the “impossible” position in conditional types and as a placeholder.
import { Schema } from "effect"
// Schema.decodeUnknownSync(Schema.Never)(1) -> throws (no value is valid)Refined primitives (still non-transforming)
Section titled “Refined primitives (still non-transforming)”These attach a built-in filter to a base primitive. They validate more strictly but do not change the type — decode/encode still pass the value through.
Schema.Finite
Section titled “Schema.Finite”Schema.Number that rejects NaN, Infinity, and -Infinity.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Finite)(3.14) // => 3.14// Schema.decodeUnknownSync(Schema.Finite)(Infinity) -> throwsSchema.Int
Section titled “Schema.Int”Schema.Number that requires an integer (also rejects NaN/Infinity).
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Int)(7) // => 7// Schema.decodeUnknownSync(Schema.Int)(7.5) -> throwsSchema.NonEmptyString
Section titled “Schema.NonEmptyString”Schema.String with at least one character.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.NonEmptyString)("a") // => "a"// Schema.decodeUnknownSync(Schema.NonEmptyString)("") -> throwsSchema.Char
Section titled “Schema.Char”Schema.String whose JavaScript .length is exactly 1. Note this counts
UTF-16 code units, so some single visible glyphs (emoji, combined characters) do
not qualify.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Char)("x") // => "x"// Schema.decodeUnknownSync(Schema.Char)("xy") -> throwsBuilt-in class schemas (instanceof)
Section titled “Built-in class schemas (instanceof)”These validate that a value is an instance of a well-known JavaScript class. The
decoded Type is the class instance and decoding passes it through unchanged. (A
default JSON serializer is attached for each — see the note per schema — but that
only matters when you go through the JSON codec layer.)
Schema.Date
Section titled “Schema.Date”Accepts any Date instance, including invalid dates like
new Date("nope"). Use DateValid to reject those, or
Schema.DateFromString (in transformations) to decode
from an ISO string.
import { Schema } from "effect"
const d = new Date("2024-01-01")Schema.decodeUnknownSync(Schema.Date)(d) // => Date 2024-01-01T00:00:00.000Z// Schema.decodeUnknownSync(Schema.Date)("2024-01-01") -> throws (string, not a Date)Schema.DateValid
Section titled “Schema.DateValid”Schema.Date that additionally rejects invalid Date instances.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.DateValid)(new Date("2024-01-01")) // => Date ...// Schema.decodeUnknownSync(Schema.DateValid)(new Date("nope")) -> throwsSchema.RegExp
Section titled “Schema.RegExp”Accepts any RegExp instance. The JSON serializer encodes it as
{ source, flags }.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.RegExp)(/ab+c/i) // => /ab+c/iSchema.URL
Section titled “Schema.URL”Accepts any URL instance. The JSON serializer encodes it as a string. To decode
a URL from a string, use Schema.URLFromString
(transformations).
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.URL)(new URL("https://effect.website")) // => URL ...Schema.Uint8Array
Section titled “Schema.Uint8Array”Accepts any Uint8Array. The JSON serializer encodes it as a Base64 string.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Uint8Array)(new Uint8Array([1, 2, 3])) // => Uint8Array(3) [1, 2, 3]Schema.File
Section titled “Schema.File”Accepts any File instance. The JSON serializer encodes it as
{ data, type, name, lastModified } with data Base64-encoded.
import { Schema } from "effect"
const file = new File(["hi"], "greeting.txt", { type: "text/plain" })Schema.decodeUnknownSync(Schema.File)(file) // => File ...Schema.FormData
Section titled “Schema.FormData”Accepts any FormData instance. The JSON serializer encodes it as an array of
[key, entry] pairs.
import { Schema } from "effect"
const form = new FormData()form.append("name", "Ada")Schema.decodeUnknownSync(Schema.FormData)(form) // => FormData ...Schema.URLSearchParams
Section titled “Schema.URLSearchParams”Accepts any URLSearchParams instance. The JSON serializer encodes it as a query
string.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.URLSearchParams)( new URLSearchParams("page=1&limit=20")) // => URLSearchParams { 'page' => '1', 'limit' => '20' }Schema.instanceOf
Section titled “Schema.instanceOf”Build an instanceof schema for any class, including your own. Decoding and
encoding pass the value through unchanged.
import { Schema } from "effect"
class Point { constructor(readonly x: number, readonly y: number) {}}
const PointSchema = Schema.instanceOf(Point)
Schema.decodeUnknownSync(PointSchema)(new Point(1, 2)) // => Point { x: 1, y: 2 }// Schema.decodeUnknownSync(PointSchema)({ x: 1, y: 2 }) -> throws (not a Point)JSON values
Section titled “JSON values”Schema.Json
Section titled “Schema.Json”Accepts any immutable JSON-compatible value: null, number, boolean,
string, readonly arrays of Json, or readonly string-keyed records of Json.
import { Schema } from "effect"
const r = Schema.decodeUnknownOption(Schema.Json)({ key: [1, true, null] })console.log(r._tag) // => "Some"Schema.MutableJson
Section titled “Schema.MutableJson”Like Json but the arrays and records in its type are mutable.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.MutableJson)([1, "two", false]) // => [1, "two", false]Literals & enums
Section titled “Literals & enums”Schema.Literal
Section titled “Schema.Literal”Matches one exact value. Accepts a single string, number, bigint, boolean, or
null.
import { Schema } from "effect"
const Yes = Schema.Literal("yes")Schema.decodeUnknownSync(Yes)("yes") // => "yes"// Schema.decodeUnknownSync(Yes)("no") -> throws
// Read the literal back off the schema:Yes.literal // => "yes"Schema.Literals
Section titled “Schema.Literals”A union of literal values — the idiomatic way to model a closed set of constants.
The members are tried in order; .pick narrows to a subset.
import { Schema } from "effect"
const Status = Schema.Literals(["active", "inactive", "pending"])type Status = typeof Status.Type // "active" | "inactive" | "pending"
Schema.decodeUnknownSync(Status)("active") // => "active"
// Narrow to a subset, preserving the literal type:const Live = Status.pick(["active", "pending"])type Live = typeof Live.Type // "active" | "pending"
// Access the underlying literal array and member schemas:Status.literals // => ["active", "inactive", "pending"]Schema.Enum
Section titled “Schema.Enum”Builds a schema from a TypeScript enum, accepting any of its values.
import { Schema } from "effect"
enum Direction { Up = "UP", Down = "DOWN"}
const DirectionSchema = Schema.Enum(Direction)
Schema.decodeUnknownSync(DirectionSchema)(Direction.Up) // => "UP"Schema.decodeUnknownSync(DirectionSchema)("DOWN") // => "DOWN"// Schema.decodeUnknownSync(DirectionSchema)("LEFT") -> throws
DirectionSchema.enums // => the original enum objectSchema.UniqueSymbol
Section titled “Schema.UniqueSymbol”Matches one specific symbol (and only that symbol). For any symbol, use
Symbol.
import { Schema } from "effect"
const Brand = Symbol.for("Brand")const BrandSchema = Schema.UniqueSymbol(Brand)
Schema.decodeUnknownSync(BrandSchema)(Brand) // => Symbol(Brand)// Schema.decodeUnknownSync(BrandSchema)(Symbol.for("Other")) -> throwsTemplate literals
Section titled “Template literals”Schema.TemplateLiteral
Section titled “Schema.TemplateLiteral”Validates strings matching a template-literal pattern. Each part is either a
literal string/number/bigint or a schema whose encoded type is a string,
number, or bigint. The value stays a string — this only validates the shape.
import { Schema } from "effect"
const Route = Schema.TemplateLiteral(["/user/", Schema.Number])// Type: `/user/${number}`
Schema.decodeUnknownSync(Route)("/user/123") // => "/user/123"// Schema.decodeUnknownSync(Route)("/user/abc") -> throws
Route.parts // => ["/user/", Schema.Number]Schema.TemplateLiteralParser
Section titled “Schema.TemplateLiteralParser”Same matching as TemplateLiteral, but decodes the matched parts into a
readonly tuple (one element per part). Literal parts contribute their literal
value; schema parts contribute their decoded Type — so use a decoding schema
like Schema.NumberFromString to actually convert a captured segment.
import { Schema } from "effect"
const Route = Schema.TemplateLiteralParser(["/user/", Schema.NumberFromString])
// Decode: string -> typed tupleSchema.decodeUnknownSync(Route)("/user/42") // => ["/user/", 42]
// Encode: tuple -> stringSchema.encodeSync(Route)(["/user/", 42]) // => "/user/42"Unions
Section titled “Unions”Schema.Union
Section titled “Schema.Union”Accepts a value matching any member. By default (mode: "anyOf") members are
tried in order and the first match wins; pass mode: "oneOf" to require that
exactly one member matches.
import { Schema } from "effect"
const StringOrNumber = Schema.Union([Schema.String, Schema.Number])
Schema.decodeUnknownSync(StringOrNumber)("hello") // => "hello"Schema.decodeUnknownSync(StringOrNumber)(42) // => 42
// Access / transform members:StringOrNumber.members // => [Schema.String, Schema.Number]Schema.NullOr
Section titled “Schema.NullOr”Schema.Union([schema, Schema.Null]) — the value or null.
import { Schema } from "effect"
const Name = Schema.NullOr(Schema.String) // string | nullSchema.decodeUnknownSync(Name)(null) // => nullSchema.decodeUnknownSync(Name)("Ada") // => "Ada"Schema.UndefinedOr
Section titled “Schema.UndefinedOr”Schema.Union([schema, Schema.Undefined]) — the value or undefined.
import { Schema } from "effect"
const Bio = Schema.UndefinedOr(Schema.String) // string | undefinedSchema.decodeUnknownSync(Bio)(undefined) // => undefinedSchema.NullishOr
Section titled “Schema.NullishOr”Schema.Union([schema, Schema.Null, Schema.Undefined]) — the value, null, or
undefined.
import { Schema } from "effect"
const Avatar = Schema.NullishOr(Schema.String) // string | null | undefinedSchema.decodeUnknownSync(Avatar)(null) // => nullSchema.decodeUnknownSync(Avatar)(undefined) // => undefinedThe
OptionFrom*combinators (decodingnull/undefined/missing into anOption) are codecs, so they live in transformations. For discriminated unions keyed by a_tagfield, useSchema.TaggedUnion, covered in structs and records.
Custom primitives: declare
Section titled “Custom primitives: declare”When none of the built-ins fit, Schema.declare builds an opaque schema from a
type-guard. The schema accepts any value and succeeds when the guard returns
true. It’s the lowest-level primitive constructor — instanceOf is built on top
of it.
import { Schema } from "effect"
type UserId = string & { readonly _tag: "UserId" }
const isUserId = (u: unknown): u is UserId => typeof u === "string" && u.startsWith("user_")
const UserId = Schema.declare<UserId>(isUserId, { title: "UserId", description: "A user identifier starting with 'user_'"})
Schema.decodeUnknownSync(UserId)("user_42") // => "user_42"// Schema.decodeUnknownSync(UserId)("nope") -> throwsFor parametric types — Option<A>, a custom Box<A>, etc. — use
Schema.declareConstructor, which threads the inner type parameters’ schemas
through. This is an advanced escape hatch; reach for it only when a type has type
parameters that must themselves be validated.
Where to go next
Section titled “Where to go next”- Decode primitives from strings (
NumberFromString,DateFromString, the*FromStringfamily) and convert toOption/Map/Set: transformations. - Tighten a primitive with custom validations: filters.
- Assemble primitives into objects and collections: structs and records.