Derivations
A schema is a complete, machine-readable description of your data. Because of that, Effect can derive other artifacts directly from it — you write the schema once and get an equivalence, a test-data generator, a human formatter, a JSON codec, a JSON Patch differ, JSON Schema, and Standard Schema interop, all for free.
Every derivation lives on the Schema namespace as a to* function. Each one
takes a schema and returns a plain value (an Equivalence, a FastCheck.Arbitrary,
a function, a Codec, a Differ, etc.) that you can use anywhere.
The common case: one schema, many artifacts
Section titled “The common case: one schema, many artifacts”Define a schema once, then derive whatever you need from it.
import { Schema } from "effect"import * as FastCheck from "fast-check"
const Person = Schema.Struct({ id: Schema.Number, name: Schema.String})
// Structural equalityconst eq = Schema.toEquivalence(Person)eq({ id: 1, name: "Alice" }, { id: 1, name: "Alice" }) // => trueeq({ id: 1, name: "Alice" }, { id: 2, name: "Alice" }) // => false
// Random test data for property-based testsconst arb = Schema.toArbitrary(Person)FastCheck.sample(arb, 1)[0] // => { id: -12, name: "..." } (random)
// Human-readable formattingconst format = Schema.toFormatter(Person)format({ id: 1, name: "Alice" }) // => '{ "id": 1, "name": "Alice" }'
// A canonical JSON codec (Type <-> Json)const json = Schema.toCodecJson(Person)
// A JSON Schema documentconst jsonSchema = Schema.toJsonSchemaDocument(Person)These derivations are structural: they walk the schema’s AST and recurse
into structs, arrays, unions, and declarations. When the default behaviour is
not right for a given type, attach a custom one with the matching overrideTo*
helper (see Overriding a derivation).
Equivalence
Section titled “Equivalence”toEquivalence
Section titled “toEquivalence”Derives an Equivalence.Equivalence<T> that compares two
values field-by-field according to the schema structure. Nested structs, arrays,
and unions are compared recursively.
import { Schema } from "effect"
const eq = Schema.toEquivalence( Schema.Struct({ id: Schema.Number, name: Schema.String }))
eq({ id: 1, name: "Alice" }, { id: 1, name: "Alice" }) // => trueeq({ id: 1, name: "Alice" }, { id: 2, name: "Alice" }) // => falseoverrideToEquivalence
Section titled “overrideToEquivalence”Attaches a custom equivalence to a schema so that toEquivalence uses it instead
of the derived structural comparison. Useful when two values should be treated as
equal even though they are not field-by-field identical.
import { Schema } from "effect"
// Compare names case-insensitivelyconst Name = Schema.String.pipe( Schema.overrideToEquivalence(() => (a, b) => a.toLowerCase() === b.toLowerCase()))
const eq = Schema.toEquivalence(Name)eq("Alice", "alice") // => trueeq("Alice", "Bob") // => falseArbitraries (test data)
Section titled “Arbitraries (test data)”Derived arbitraries are fast-check Arbitrary
values, ready to feed into property-based tests. See
Writing tests for how to run them with
@effect/vitest.
toArbitrary
Section titled “toArbitrary”Derives a FastCheck.Arbitrary<T> that generates values satisfying the schema
(including any refinements such as length or range checks). Use this when you
just need the arbitrary directly.
import { Schema } from "effect"import * as FastCheck from "fast-check"
const PersonArb = Schema.toArbitrary( Schema.Struct({ name: Schema.String, age: Schema.Number }))
const sample = FastCheck.sample(PersonArb, 1)[0]typeof sample.name // => "string"typeof sample.age // => "number"toArbitraryLazy
Section titled “toArbitraryLazy”Derives a LazyArbitrary<T> — a function (fc) => FastCheck.Arbitrary<T> — that
defers instantiation until you pass it the fast-check module. Prefer this when
you need to control which fast-check instance is used, or for recursive schemas.
The result is memoized, so repeated calls are cheap.
import { Schema } from "effect"import * as FastCheck from "fast-check"
const lazy = Schema.toArbitraryLazy(Schema.String)
const arb = lazy(FastCheck) // => FastCheck.Arbitrary<string>FastCheck.sample(arb, 1)[0] // => "..." (random string)Formatter (human-readable strings)
Section titled “Formatter (human-readable strings)”toFormatter
Section titled “toFormatter”Derives a Formatter<T> — a function (value: T) => string — that renders a
value as a human-readable string, recursing into structs, arrays, and unions.
Strings are quoted, bigint literals get an n suffix, Option shows as
some(...) / none(), and so on. This is distinct from
issue/error formatting, which renders validation
failures.
import { Schema } from "effect"
const format = Schema.toFormatter( Schema.Struct({ id: Schema.Number, name: Schema.String }))
format({ id: 1, name: "Alice" }) // => '{ "id": 1, "name": "Alice" }'
Schema.toFormatter(Schema.String)("a") // => '"a"'Schema.toFormatter(Schema.BigInt)(1n) // => "1n"The optional onBefore hook lets you intercept specific AST nodes before the
default formatting runs:
import { Schema } from "effect"
const format = Schema.toFormatter(Schema.Number, { onBefore: (ast) => (ast._tag === "Number" ? (n) => `#${n}` : undefined)})
format(42) // => "#42"overrideToFormatter
Section titled “overrideToFormatter”Attaches a custom formatter to a schema so that toFormatter uses it instead of
the structural default.
import { Schema } from "effect"
const Money = Schema.Number.pipe( Schema.overrideToFormatter(() => (cents) => `$${(cents / 100).toFixed(2)}`))
Schema.toFormatter(Money)(1299) // => "$12.99"Representation
Section titled “Representation”toRepresentation
Section titled “toRepresentation”Derives a SchemaRepresentation.Document — an intermediate, structural
representation of the schema (a representation plus its named references).
This is the IR that toJsonSchemaDocument is built on;
reach for it directly when you want to walk or transform the schema’s shape
yourself.
import { Schema } from "effect"
const doc = Schema.toRepresentation( Schema.Struct({ name: Schema.String }))
doc.representation // => the structural representationdoc.references // => named references ($ref map)Alternate codecs
Section titled “Alternate codecs”These derivations produce a new Codec whose encoded side is a canonical
serialization format. The decoded (Type) side is unchanged, so you can decode
and encode with the usual parsing APIs
(Schema.encodeSync, Schema.decodeSync, etc.).
toCodecJson
Section titled “toCodecJson”Derives a canonical Codec<T, Json> — encoding produces a JSON-compatible value
and decoding reconstructs the schema’s Type. Types that are not natively JSON
(dates, URL, bigint, Map, Set, …) are encoded through their built-in
JSON representation.
import { Schema } from "effect"
const codec = Schema.toCodecJson( Schema.Struct({ when: Schema.Date, name: Schema.String }))
const json = Schema.encodeSync(codec)({ when: new Date("2024-01-01T00:00:00.000Z"), name: "Alice"})// => { when: "2024-01-01T00:00:00.000Z", name: "Alice" }
Schema.decodeSync(codec)(json)// => { when: Date(2024-01-01...), name: "Alice" }toCodecIso
Section titled “toCodecIso”Derives an isomorphic Codec<Type, Iso>. The encoded side is the schema’s Iso
type — the intermediate representation used for lossless round-tripping (no
information is lost in either direction). For most schemas the Iso and Type
sides coincide; declarations may define a richer Iso form.
import { Schema } from "effect"
const codec = Schema.toCodecIso(Schema.Option(Schema.Number))
const iso = Schema.encodeSync(codec)const back = Schema.decodeSync(codec)// `iso`/`back` round-trip Option values through the Iso form losslesslyoverrideToCodecIso
Section titled “overrideToCodecIso”Overrides the derived ISO codec with an explicit target codec and a
decode / encode getter pair. The resulting schema carries a custom Iso
type parameter.
import { Schema, SchemaGetter } from "effect"
// Expose a Date as an ISO string on the Iso sideconst MyDate = Schema.Date.pipe( Schema.overrideToCodecIso(Schema.String, { decode: SchemaGetter.transform((s: string) => new Date(s)), encode: SchemaGetter.transform((d: Date) => d.toISOString()) }))
Schema.encodeSync(Schema.toCodecIso(MyDate))(new Date("2024-01-01T00:00:00.000Z"))// => "2024-01-01T00:00:00.000Z"toCodecStringTree
Section titled “toCodecStringTree”Derives a Codec<T, StringTree> — every leaf value becomes a string while the
surrounding structure (objects, arrays) is preserved. This is the format behind
URL query-string and form-data encoding. Declarations are converted to
undefined unless they carry a toCodecJson / toCodec annotation.
import { Schema } from "effect"
const codec = Schema.toCodecStringTree( Schema.Struct({ page: Schema.Number, q: Schema.String }))
Schema.encodeSync(codec)({ page: 2, q: "effect" })// => { page: "2", q: "effect" } // every leaf is a string
Schema.decodeSync(codec)({ page: "2", q: "effect" })// => { page: 2, q: "effect" }Pass { keepDeclarations: true } to preserve non-string declaration values
(numbers, Blob, …) when they are compatible with the schema — this is what
Schema.fromFormData uses to keep uploaded files intact:
import { Schema } from "effect"
const codec = Schema.toCodecStringTree( Schema.Struct({ a: Schema.Int }), { keepDeclarations: true })toEncoderXml
Section titled “toEncoderXml”Derives an XML encoder from a codec. The returned function encodes a value
through toCodecStringTree and yields an Effect that succeeds with an XML
string (or fails with SchemaError if encoding fails). Options control the root
element name, array item name, pretty-printing, indentation, and key sorting.
import { Effect, Schema } from "effect"
const toXml = Schema.toEncoderXml( Schema.Struct({ name: Schema.String, age: Schema.Number }), { rootName: "person" })
Effect.runPromise(toXml({ name: "Alice", age: 30 })).then(console.log)// => <person>// => <age>30</age>// => <name>Alice</name>// => </person>Differ (JSON Patch)
Section titled “Differ (JSON Patch)”toDifferJsonPatch
Section titled “toDifferJsonPatch”Derives a Differ<T, JsonPatch.JsonPatch> from a codec. It
serializes values to JSON (via toCodecJson), computes RFC 6902
JSON Patch operations between an old and new value with diff, and can apply a
patch back to the typed value with patch. patch returns the original
reference when nothing changed.
import { Schema } from "effect"
const differ = Schema.toDifferJsonPatch( Schema.Struct({ a: Schema.String, b: Schema.Number }))
const patch = differ.diff({ a: "x", b: 1 }, { a: "x", b: 2 })// => [{ op: "replace", path: "/b", value: 2 }]
differ.patch({ a: "x", b: 1 }, patch)// => { a: "x", b: 2 }
differ.empty // => [] (no-op patch)differ.combine(patch, differ.empty) // => patch (concatenated ops)Standard Schema & JSON Schema
Section titled “Standard Schema & JSON Schema”toStandardSchemaV1
Section titled “toStandardSchemaV1”Produces a Standard Schema v1 object so the schema
can be consumed by any Standard-Schema-compatible library (form libraries,
validators, etc.). The returned object exposes a ~standard.validate method.
Optional leafHook / checkHook customize issue messages, and parseOptions
forwards parse settings (defaults to collecting all errors).
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.NonEmptyString, age: Schema.Number})
const standard = Schema.toStandardSchemaV1(Person)
standard["~standard"].validate({ name: "Alice", age: 30 })// => { value: { name: "Alice", age: 30 } }
standard["~standard"].validate({ name: "", age: 30 })// => { issues: [{ path: ["name"], message: "..." }] }toStandardJSONSchemaV1
Section titled “toStandardJSONSchemaV1”Produces an (experimental) Standard JSON Schema v1 object. It attaches a
~standard.jsonSchema accessor with input / output methods that emit JSON
Schema for the encoded and decoded sides, targeting either draft-2020-12 or
draft-07.
import { Schema } from "effect"
const Person = Schema.Struct({ name: Schema.String })const standard = Schema.toStandardJSONSchemaV1(Person)
standard["~standard"].jsonSchema.input({ target: "draft-2020-12" })// => { type: "object", properties: { name: { type: "string" } }, ... }toJsonSchemaDocument
Section titled “toJsonSchemaDocument”Returns a full JSON Schema document (draft 2020-12) with a schema and a
definitions map. This is the primary JSON Schema entry point — see the dedicated
JSON Schema page for options
(additionalProperties, generateDescriptions, includeAnnotationKey) and a
deeper walk-through.
import { Schema } from "effect"
const document = Schema.toJsonSchemaDocument( Schema.Struct({ name: Schema.String }))
document.dialect // => "draft-2020-12"document.schema // => { type: "object", properties: { name: ... }, ... }document.definitions // => {} (named $defs, if any)Optics
Section titled “Optics”These derive optics from a schema’s Iso boundary, letting you focus into the
serialized form.
Derives an Iso<Type, Iso> optic that isomorphically converts between the
schema’s Type and its Iso (intermediate / serialized) form, built on top of
toCodecIso.
import { Schema } from "effect"
const iso = Schema.toIso(Schema.Option(Schema.Number))// => Iso focusing the Iso representation of Option<number>toIsoSource
Section titled “toIsoSource”Returns the identity Iso<Type, Type> over the schema’s source (decoded) side —
a convenient starting point when composing optics that stay on the Type side.
import { Schema } from "effect"
const iso = Schema.toIsoSource(Schema.String) // => Iso<string, string>toIsoFocus
Section titled “toIsoFocus”Returns the identity Iso<Iso, Iso> over the schema’s focus (encoded Iso)
side — the counterpart to toIsoSource for composing on the Iso side.
import { Schema } from "effect"
const iso = Schema.toIsoFocus(Schema.String) // => Iso over the Iso sideOverriding a derivation
Section titled “Overriding a derivation”Every structural derivation can be customized per-schema by attaching an
annotation through the matching overrideTo* helper. The override is picked up
the next time you call the corresponding to* function on that schema (or any
schema that contains it).
| Derivation | Override helper |
|---|---|
toEquivalence | overrideToEquivalence |
toFormatter | overrideToFormatter |
toCodecIso | overrideToCodecIso |
import { Schema } from "effect"
// One schema, two overrides — both flow through the derivations belowconst Temperature = Schema.Number.pipe( Schema.overrideToFormatter(() => (c) => `${c}°C`), Schema.overrideToEquivalence(() => (a, b) => Math.round(a) === Math.round(b)))
Schema.toFormatter(Temperature)(21.4) // => "21.4°C"Schema.toEquivalence(Temperature)(21.4, 21.0) // => true