Transformations
A transformation connects two schemas with a pair of functions — one that runs
while decoding (Encoded → Type) and one that runs while encoding
(Type → Encoded). This is how a schema can store a date as a string on the
wire yet hand you a real Date, or accept "42" and give you the number 42.
import { Schema, SchemaTransformation } from "effect"
// numberFromString pairs two getters:// - decode runs Encoded -> Type (here: string -> number)// - encode runs Type -> Encoded (here: number -> string)const NumberFromString = Schema.String.pipe( Schema.decodeTo(Schema.Number, SchemaTransformation.numberFromString))
console.log(Schema.decodeUnknownSync(NumberFromString)("123")) // => 123 (number)console.log(Schema.encodeUnknownSync(NumberFromString)(123)) // => "123" (string)Concretely, a SchemaTransformation.Transformation<T, E> is just a pair of
SchemaGetter.Getters — decode: Getter<T, E> and encode: Getter<E, T>. A
Getter<T, E> is a single-direction function Option<E> → Effect<Option<T>, Issue>
that can fail with a SchemaIssue, skip absent keys (Option.None), and require
Effect services. The bulk of the work is choosing or writing those two getters.
decodeTo
Section titled “decodeTo”Schema.decodeTo(to, transformation) is curried: you call it with the target
schema and then pipe the source schema into it. The result has
Encoded = from.Encoded and Type = to.Type.
You can pass either a ready-made SchemaTransformation, or a plain object with
decode/encode getters (which make wraps for you).
import { Schema, SchemaGetter } from "effect"
// Provide the two getters directly:// decode: string -> number encode: number -> stringconst NumberFromString = Schema.String.pipe( Schema.decodeTo(Schema.Number, { decode: SchemaGetter.Number(), encode: SchemaGetter.String() }))
console.log(Schema.decodeUnknownSync(NumberFromString)("42")) // => 42console.log(Schema.encodeUnknownSync(NumberFromString)(42)) // => "42"Schema.decode (same Type and Encoded)
Section titled “Schema.decode (same Type and Encoded)”Schema.decode is a convenience for transformations where the encoded and
decoded types are the same (e.g. string → string). Pass a single
Transformation or a { decode, encode } getter pair.
import { Schema, SchemaGetter } from "effect"
// Trim surrounding whitespace while decoding; leave the value alone on encode.const TrimmedString = Schema.String.pipe( Schema.decode({ decode: SchemaGetter.trim(), encode: SchemaGetter.passthrough() }))
console.log(Schema.decodeUnknownSync(TrimmedString)(" hello ")) // => "hello"Transformations that can fail
Section titled “Transformations that can fail”Decoding often involves parsing that may not succeed. Use
SchemaTransformation.transformOrFail (or SchemaGetter.transformOrFail for a
single getter): the function returns an Effect and reports failures as a
SchemaIssue. The failure is woven into the normal decode error tree, so it
benefits from path tracking and error formatting.
import { Effect, Option, Schema, SchemaIssue, SchemaTransformation } from "effect"
// A schema that decodes a string into an integer, failing cleanly on bad input.const IntFromString = Schema.String.pipe( Schema.decodeTo( Schema.Number, SchemaTransformation.transformOrFail({ decode: (s) => { const n = Number.parseInt(s, 10) return Number.isNaN(n) ? Effect.fail( new SchemaIssue.InvalidValue(Option.some(s), { message: "expected an integer string" }) ) : Effect.succeed(n) }, encode: (n) => Effect.succeed(String(n)) }) ))
console.log(Schema.decodeUnknownSync(IntFromString)("7")) // => 7// Schema.decodeUnknownSync(IntFromString)("abc") -> throws "expected an integer string"encodeTo
Section titled “encodeTo”Schema.encodeTo is the mirror image: it lets you name the encoded schema
first. to.pipe(Schema.encodeTo(encodedSchema, { decode, encode })) reads
naturally when you think of a value being encoded outward. It is otherwise
equivalent to decodeTo with the schemas swapped — the decode getter still runs
Encoded → Type, the encode getter still runs Type → Encoded.
import { Schema, SchemaGetter } from "effect"
const NumberFromString = Schema.Number.pipe( Schema.encodeTo(Schema.String, { decode: SchemaGetter.transform((s: string) => Number(s)), encode: SchemaGetter.transform((n: number) => String(n)) }))
console.log(Schema.decodeUnknownSync(NumberFromString)("3.14")) // => 3.14console.log(Schema.encodeUnknownSync(NumberFromString)(3.14)) // => "3.14"Schema.encode is the Type === Encoded convenience that mirrors Schema.decode.
Composing transformations
Section titled “Composing transformations”Because a transformation produces a regular schema, you can keep piping. A common pattern is to parse a JSON string and then validate its shape — chaining a string→JSON transformation into a struct schema.
import { Schema } from "effect"
const Payload = Schema.Struct({ id: Schema.String, count: Schema.Number})
// Encoded: a JSON string -> Type: the validated Payload object.const PayloadFromJson = Schema.fromJsonString(Payload)
console.log(Schema.decodeUnknownSync(PayloadFromJson)('{"id":"a","count":3}'))// => { id: "a", count: 3 }Transformations also compose at the value level: Transformation#compose chains
two transformations (decode left-to-right, encode right-to-left), and
Getter#compose does the same for individual getters.
import { SchemaTransformation } from "effect"
// decode: trim then lowercase; encode: passthrough on bothconst trimAndLower = SchemaTransformation.trim().compose( SchemaTransformation.toLowerCase())Authoring transformations: SchemaTransformation
Section titled “Authoring transformations: SchemaTransformation”The SchemaTransformation module builds the bidirectional value that
decodeTo/encodeTo/decode/encode/link consume. Reach for the built-in
transforming schemas first; drop down to these
constructors when you need a conversion that is not already provided.
Pairs two existing SchemaGetter.Getters into a Transformation. Returns its
input unchanged if it is already a Transformation (idempotent).
import { SchemaGetter, SchemaTransformation } from "effect"
const t = SchemaTransformation.make({ decode: SchemaGetter.transform<number, string>((s) => Number(s)), encode: SchemaGetter.transform<string, number>((n) => String(n))})transform
Section titled “transform”Builds a Transformation from pure, infallible decode/encode functions. The
most common constructor.
import { Schema, SchemaTransformation } from "effect"
const CentsFromDollars = Schema.Number.pipe( Schema.decodeTo( Schema.Number, SchemaTransformation.transform({ decode: (dollars) => dollars * 100, // => 100 for 1 encode: (cents) => cents / 100 // => 1 for 100 }) ))
console.log(Schema.decodeUnknownSync(CentsFromDollars)(1)) // => 100console.log(Schema.encodeUnknownSync(CentsFromDollars)(100)) // => 1transformOrFail
Section titled “transformOrFail”Builds a Transformation from effectful decode/encode functions that may fail
with a SchemaIssue or require services. See the
fallible example above.
import { Effect, SchemaTransformation } from "effect"
const t = SchemaTransformation.transformOrFail({ decode: (s: string) => Effect.succeed(s.length), encode: (n: number) => Effect.succeed("x".repeat(n))})transformOptional
Section titled “transformOptional”Operates on Option values directly, giving full control over missing-key
handling. Option.None input means the key is absent; returning Option.None
omits the key from output.
import { Option, Schema, SchemaTransformation } from "effect"
const schema = Schema.Struct({ a: Schema.optionalKey(Schema.Number).pipe( Schema.decodeTo( Schema.Option(Schema.Number), SchemaTransformation.transformOptional({ decode: Option.some, // absent -> Some(None), present -> Some(Some(v)) encode: Option.flatten }) ) )})passthrough / passthroughSupertype / passthroughSubtype
Section titled “passthrough / passthroughSupertype / passthroughSubtype”The identity transformation — no conversion in either direction. Used to connect
two schemas that share a runtime type. passthrough requires T === E; pass
{ strict: false } to bypass, or use the typed passthroughSupertype
(T extends E) / passthroughSubtype (E extends T) variants.
import { Schema, SchemaTransformation } from "effect"
const schema = Schema.Trim.pipe( Schema.decodeTo(Schema.FiniteFromString, SchemaTransformation.passthrough()))
// Typed, no runtime change:const sup = SchemaTransformation.passthroughSupertype<"a" | "b", string>()const sub = SchemaTransformation.passthroughSubtype<string, "a" | "b">()String transformation shortcuts
Section titled “String transformation shortcuts”These pair a string getter on decode with passthrough on encode (so they are
lossy and do not round-trip), except snakeToCamel and splitKeyValue,
which provide a real inverse.
trim()— strips surrounding whitespace on decode.toLowerCase()/toUpperCase()— case-normalize on decode.capitalize()/uncapitalize()— change the first character on decode.snakeToCamel()—"my_field"↔"myField"(real inverse on encode).splitKeyValue(options?)—string↔Record<string, string>.
import { Schema, SchemaTransformation } from "effect"
const Config = Schema.String.pipe( Schema.decodeTo( Schema.Record(Schema.String, Schema.String), SchemaTransformation.splitKeyValue({ separator: ";", keyValueSeparator: ":" }) ))
console.log(Schema.decodeUnknownSync(Config)("host:localhost;port:3000"))// => { host: "localhost", port: "3000" }isTransformation
Section titled “isTransformation”Type guard: true if a value is a Transformation instance.
import { SchemaTransformation } from "effect"
SchemaTransformation.isTransformation(SchemaTransformation.trim()) // => trueSchemaTransformation.isTransformation({ decode: null }) // => falseMiddleware: middlewareDecoding / middlewareEncoding
Section titled “Middleware: middlewareDecoding / middlewareEncoding”A Transformation operates on individual values; a Middleware wraps the whole
parsing Effect for a direction, so it can catch errors, retry, or run side
effects around the pipeline. You usually install one via
Schema.middlewareDecoding / Schema.middlewareEncoding rather than constructing
SchemaTransformation.Middleware by hand.
import { Effect, Option, Schema } from "effect"
// Recover from a decode failure with a fallback value.const WithFallback = Schema.String.pipe( Schema.middlewareDecoding( Effect.catch(() => Effect.succeed(Option.some("fallback"))) ))The Getter toolkit: SchemaGetter
Section titled “The Getter toolkit: SchemaGetter”A Getter<T, E, R> is one direction of a transformation. You combine two of them
(decode + encode) in a decodeTo/encodeTo object, or via
SchemaTransformation.make. These are the primitives the built-ins are built from.
succeed
Section titled “succeed”Always produces a constant, ignoring the input.
import { SchemaGetter } from "effect"
const alwaysZero = SchemaGetter.succeed(0) // => Some(0) for any inputAlways fails with the Issue returned by the supplied function.
import { Option, SchemaGetter, SchemaIssue } from "effect"
const rejectAll = SchemaGetter.fail<string, string>( (oe) => new SchemaIssue.InvalidValue(oe, { message: "not allowed" }))forbidden
Section titled “forbidden”Always fails with a Forbidden issue — handy for disabling one direction.
import { SchemaGetter } from "effect"
const noEncode = SchemaGetter.forbidden<string, number>(() => "encoding is not supported")transform
Section titled “transform”Pure, infallible map of present values (Some(e) → Some(f(e)), None untouched).
import { SchemaGetter } from "effect"
const double = SchemaGetter.transform<number, number>((n) => n * 2) // => Some(6) for 3transformOrFail
Section titled “transformOrFail”Fallible/effectful map of present values; propagates the Issue on failure.
import { Effect, Option, SchemaGetter, SchemaIssue } from "effect"
const parseInt = SchemaGetter.transformOrFail<number, string>((s) => { const n = Number.parseInt(s, 10) return Number.isNaN(n) ? Effect.fail(new SchemaIssue.InvalidValue(Option.some(s), { message: "not an integer" })) : Effect.succeed(n)})transformOptional
Section titled “transformOptional”Pure transform over the full Option<E> → Option<T>, so you can turn present
into absent and vice versa.
import { Option, SchemaGetter } from "effect"
const skipEmpty = SchemaGetter.transformOptional<string, string>((o) => Option.filter(o, (s) => s.length > 0))onNone / onSome / required
Section titled “onNone / onSome / required”onNone supplies a value when the key is absent; onSome runs only for present
values; required fails with MissingKey when absent.
import { Effect, Option, SchemaGetter } from "effect"
const withTimestamp = SchemaGetter.onNone<number>(() => Effect.succeed(Option.some(Date.now())))const parseIfPresent = SchemaGetter.onSome<number, string>((s) => Effect.succeed(Option.some(Number(s))))const mustExist = SchemaGetter.required<string>() // => fails MissingKey when absentwithDefault
Section titled “withDefault”Replaces Some(undefined) / None with a default produced by an Effect.
import { Effect, SchemaGetter } from "effect"
const withZero = SchemaGetter.withDefault(Effect.succeed(0)) // Getter<number, number | undefined>Always returns None, dropping the value from output.
import { SchemaGetter } from "effect"
const omitField = SchemaGetter.omit<string>() // => NonecheckEffect
Section titled “checkEffect”Validates a present value with an effectful check, without changing it. Return
undefined/true to accept; false, a string, or an Issue to reject.
import { Effect, SchemaGetter } from "effect"
const nonNegative = SchemaGetter.checkEffect<number>((n) => Effect.succeed(n >= 0 ? undefined : "must be non-negative"))Value coercions: String / Number / Boolean / BigInt / Date
Section titled “Value coercions: String / Number / Boolean / BigInt / Date”Pure coercions delegating to the global constructors. Number may yield NaN;
BigInt and Date may throw / produce invalid values for bad input.
import { SchemaGetter } from "effect"
SchemaGetter.String<number>() // Getter<string, number> e.g. 42 => "42"SchemaGetter.Number<string>() // Getter<number, string> e.g. "42" => 42SchemaGetter.Boolean<string>() // Getter<boolean, string> e.g. "" => falseSchemaGetter.BigInt<string>() // Getter<bigint, string> e.g. "9" => 9nSchemaGetter.Date<string>() // Getter<Date, string> e.g. "2020-01-01" => DateString getters
Section titled “String getters”trim(),capitalize(),uncapitalize(),toLowerCase(),toUpperCase()— pure string ops.snakeToCamel()/camelToSnake()— case-convention conversions (inverses).split(options?)—"a,b,c"→["a", "b", "c"](empty string →[]).splitKeyValue(options?)/joinKeyValue(options?)— string ↔Record<string, string>(inverses).
import { SchemaGetter } from "effect"
const toCamel = SchemaGetter.snakeToCamel<string>() // "my_field" => "myField"const split = SchemaGetter.split<string>() // "a,b" => ["a", "b"]const parse = SchemaGetter.splitKeyValue<string>() // "a=1,b=2" => { a: "1", b: "2" }const join = SchemaGetter.joinKeyValue() // { a: "1", b: "2" } => "a=1,b=2"JSON getters: parseJson / stringifyJson
Section titled “JSON getters: parseJson / stringifyJson”parseJson parses a JSON string (fails with InvalidValue on bad JSON; accepts
an optional reviver); stringifyJson serializes (optional replacer/space).
import { SchemaGetter } from "effect"
const parse = SchemaGetter.parseJson<string>() // '{"a":1}' => { a: 1 }const stringify = SchemaGetter.stringifyJson() // { a: 1 } => '{"a":1}'Encoding getters: Base64, Hex, URI component
Section titled “Encoding getters: Base64, Hex, URI component”Decode getters fail with InvalidValue on malformed input. Each has a string and
a Uint8Array flavor.
encodeBase64()/decodeBase64()/decodeBase64String()— bytes/string ↔ Base64.encodeBase64Url()/decodeBase64Url()/decodeBase64UrlString()— URL-safe Base64.encodeHex()/decodeHex()/decodeHexString()— bytes/string ↔ hex.encodeUriComponent()/decodeUriComponent()—encodeURIComponent/decodeURIComponent.
import { SchemaGetter } from "effect"
const enc = SchemaGetter.encodeBase64<string>() // "hi" => "aGk="const dec = SchemaGetter.decodeBase64String<string>() // "aGk=" => "hi"const hex = SchemaGetter.encodeHex<string>() // "hi" => "6869"const uri = SchemaGetter.encodeUriComponent<string>() // "a b" => "a%20b"DateTime getter: dateTimeUtcFromInput
Section titled “DateTime getter: dateTimeUtcFromInput”Parses a DateTime.Input (string, epoch millis, Date, parts, …) into a
DateTime.Utc; fails with InvalidValue on unparseable input.
import { SchemaGetter } from "effect"
const parseDate = SchemaGetter.dateTimeUtcFromInput<string>() // Getter<DateTime.Utc, string>Web getters: FormData / URLSearchParams
Section titled “Web getters: FormData / URLSearchParams”Decode parses bracket-path keys (user[name], items[0]) into a nested record;
encode flattens a nested object back into bracket-path entries.
decodeFormData()/encodeFormData()decodeURLSearchParams()/encodeURLSearchParams()makeTreeRecord(entries)/collectBracketPathEntries(isLeaf)— the underlying parse/flatten helpers.
import { SchemaGetter } from "effect"
SchemaGetter.makeTreeRecord([ ["user[name]", "Alice"], ["user[tags][]", "admin"], ["user[tags][]", "editor"]])// => { user: { name: "Alice", tags: ["admin", "editor"] } }Built-in transforming schemas
Section titled “Built-in transforming schemas”These ship ready to use, so you rarely write getters by hand. Every entry below
shows both directions with inline // => results.
Strings ↔ numbers and bigints
Section titled “Strings ↔ numbers and bigints”NumberFromString
Section titled “NumberFromString”string ↔ number, allowing non-finite results like NaN / Infinity.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.NumberFromString)("3.14") // => 3.14Schema.encodeUnknownSync(Schema.NumberFromString)(3.14) // => "3.14"FiniteFromString
Section titled “FiniteFromString”Like NumberFromString but rejects NaN, Infinity, and -Infinity on decode.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.FiniteFromString)("42") // => 42// Schema.decodeUnknownSync(Schema.FiniteFromString)("Infinity") -> throwsBigIntFromString
Section titled “BigIntFromString”string ↔ bigint. Decoding accepts only strings matching ^-?\d+$.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.BigIntFromString)("9007199254740993") // => 9007199254740993nSchema.encodeUnknownSync(Schema.BigIntFromString)(9007199254740993n) // => "9007199254740993"isStringBigInt
Section titled “isStringBigInt”The string filter used by BigIntFromString — checks that a string parses as a
bigint. Apply it with Schema.check.
import { Schema } from "effect"
const BigIntString = Schema.String.check(Schema.isStringBigInt())Schema.decodeUnknownSync(BigIntString)("123") // => "123"// Schema.decodeUnknownSync(BigIntString)("1.5") -> throwsBigDecimalFromString
Section titled “BigDecimalFromString”string ↔ BigDecimal for arbitrary-precision decimals. (v4-new)
import { Schema } from "effect"
const bd = Schema.decodeUnknownSync(Schema.BigDecimalFromString)("1.5") // => BigDecimal 1.5Schema.encodeUnknownSync(Schema.BigDecimalFromString)(bd) // => "1.5"BooleanFromBit
Section titled “BooleanFromBit”0 | 1 ↔ boolean.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.BooleanFromBit)(1) // => trueSchema.encodeUnknownSync(Schema.BooleanFromBit)(false) // => 0Dates and times
Section titled “Dates and times”DateFromString
Section titled “DateFromString”string ↔ Date (via new Date / toISOString). No validity check; pair with
checks if you need one.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.DateFromString)("2020-01-01T00:00:00.000Z") // => DateSchema.encodeUnknownSync(Schema.DateFromString)(new Date(0)) // => "1970-01-01T00:00:00.000Z"DateTimeUtc
Section titled “DateTimeUtc”A declaration schema for DateTime.Utc values (no transformation; validates that
the input is already a DateTime.Utc). The building block for the codecs below.
import { DateTime, Schema } from "effect"
Schema.decodeUnknownSync(Schema.DateTimeUtc)(DateTime.makeUnsafe(0)) // => DateTime.UtcDateTimeUtcFromString
Section titled “DateTimeUtcFromString”string (ISO) ↔ DateTime.Utc; encodes with DateTime.formatIso.
import { Schema } from "effect"
const dt = Schema.decodeUnknownSync(Schema.DateTimeUtcFromString)("2020-01-01T00:00:00Z") // => DateTime.UtcSchema.encodeUnknownSync(Schema.DateTimeUtcFromString)(dt) // => "2020-01-01T00:00:00.000Z"DateTimeUtcFromDate
Section titled “DateTimeUtcFromDate”Date ↔ DateTime.Utc.
import { Schema } from "effect"
const dt = Schema.decodeUnknownSync(Schema.DateTimeUtcFromDate)(new Date(0)) // => DateTime.UtcSchema.encodeUnknownSync(Schema.DateTimeUtcFromDate)(dt) // => Date(1970-01-01T00:00:00.000Z)DateTimeUtcFromMillis
Section titled “DateTimeUtcFromMillis”number (epoch millis) ↔ DateTime.Utc.
import { Schema } from "effect"
const dt = Schema.decodeUnknownSync(Schema.DateTimeUtcFromMillis)(0) // => DateTime.Utc (epoch)Schema.encodeUnknownSync(Schema.DateTimeUtcFromMillis)(dt) // => 0DateTimeZoned
Section titled “DateTimeZoned”Declaration schema for DateTime.Zoned values (validates the input is zoned).
import { Schema } from "effect"
// Decoding requires a DateTime.Zoned value; pairs with DateTimeZonedFromString below.const schema = Schema.DateTimeZonedDateTimeZonedFromString
Section titled “DateTimeZonedFromString”ISO zoned string ↔ DateTime.Zoned; encodes with DateTime.formatIsoZoned.
import { Schema } from "effect"
const z = Schema.decodeUnknownSync(Schema.DateTimeZonedFromString)( "2020-01-01T00:00:00.000Z[Europe/London]") // => DateTime.ZonedSchema.encodeUnknownSync(Schema.DateTimeZonedFromString)(z)// => "2020-01-01T00:00:00.000Z[Europe/London]"TimeZone / TimeZoneFromString
Section titled “TimeZone / TimeZoneFromString”TimeZone is the declaration schema for any DateTime.TimeZone;
TimeZoneFromString decodes either an IANA id or an offset string (e.g.
"+03:00") into one.
import { Schema } from "effect"
const tz = Schema.decodeUnknownSync(Schema.TimeZoneFromString)("Europe/London") // => TimeZoneSchema.encodeUnknownSync(Schema.TimeZoneFromString)(tz) // => "Europe/London"TimeZoneNamed / TimeZoneNamedFromString
Section titled “TimeZoneNamed / TimeZoneNamedFromString”TimeZoneNamed declares a DateTime.TimeZone.Named; TimeZoneNamedFromString
decodes only valid IANA identifier strings into one.
import { Schema } from "effect"
const named = Schema.decodeUnknownSync(Schema.TimeZoneNamedFromString)("America/New_York") // => NamedSchema.encodeUnknownSync(Schema.TimeZoneNamedFromString)(named) // => "America/New_York"TimeZoneOffset
Section titled “TimeZoneOffset”Declaration schema for a fixed DateTime.TimeZone.Offset. Use
SchemaTransformation.timeZoneOffsetFromNumber to build a number ↔ offset codec.
import { Schema } from "effect"
const schema = Schema.TimeZoneOffset // validates DateTime.TimeZone.Offset valuesDurations (v4-new)
Section titled “Durations (v4-new)”Duration
Section titled “Duration”Declaration schema for Duration values (no conversion; validates a Duration).
import { Duration, Schema } from "effect"
Schema.decodeUnknownSync(Schema.Duration)(Duration.seconds(5)) // => Duration(5s)DurationFromString
Section titled “DurationFromString”Human-readable string ↔ Duration (e.g. "2 seconds", "10 nanos",
"Infinity"). (v4-new)
import { Schema } from "effect"
const d = Schema.decodeUnknownSync(Schema.DurationFromString)("2 seconds") // => Duration(2s)Schema.encodeUnknownSync(Schema.DurationFromString)(d) // => "2 seconds"DurationFromMillis
Section titled “DurationFromMillis”number (millis) ↔ Duration.
import { Schema } from "effect"
const d = Schema.decodeUnknownSync(Schema.DurationFromMillis)(2000) // => Duration(2s)Schema.encodeUnknownSync(Schema.DurationFromMillis)(d) // => 2000DurationFromNanos
Section titled “DurationFromNanos”bigint (nanos) ↔ Duration. Encode fails if the Duration is not
bigint-representable (e.g. Duration.infinity).
import { Schema } from "effect"
const d = Schema.decodeUnknownSync(Schema.DurationFromNanos)(2_000_000_000n) // => Duration(2s)Schema.encodeUnknownSync(Schema.DurationFromNanos)(d) // => 2000000000nBinary encodings (v4-new)
Section titled “Binary encodings (v4-new)”StringFromBase64 / StringFromBase64Url / StringFromHex
Section titled “StringFromBase64 / StringFromBase64Url / StringFromHex”Base64 (or URL-safe Base64, or hex) string ↔ UTF-8 string. Decode fails on
malformed input.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.StringFromBase64)("aGVsbG8=") // => "hello"Schema.encodeUnknownSync(Schema.StringFromBase64)("hello") // => "aGVsbG8="
Schema.decodeUnknownSync(Schema.StringFromHex)("6869") // => "hi"Schema.encodeUnknownSync(Schema.StringFromHex)("hi") // => "6869"StringFromUriComponent
Section titled “StringFromUriComponent”URI-component-encoded string ↔ decoded string. (v4-new)
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.StringFromUriComponent)("a%20b") // => "a b"Schema.encodeUnknownSync(Schema.StringFromUriComponent)("a b") // => "a%20b"Uint8ArrayFromBase64 / Uint8ArrayFromBase64Url / Uint8ArrayFromHex
Section titled “Uint8ArrayFromBase64 / Uint8ArrayFromBase64Url / Uint8ArrayFromHex”Encoded string ↔ Uint8Array for binary payloads.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Uint8ArrayFromBase64)("aGk=") // => Uint8Array [104, 105]Schema.encodeUnknownSync(Schema.Uint8ArrayFromBase64)(new Uint8Array([104, 105])) // => "aGk="
Schema.decodeUnknownSync(Schema.Uint8ArrayFromHex)("6869") // => Uint8Array [104, 105]Schema.encodeUnknownSync(Schema.Uint8ArrayFromHex)(new Uint8Array([104, 105])) // => "6869"URL / URLFromString
Section titled “URL / URLFromString”URL is the declaration schema for globalThis.URL instances; URLFromString
decodes a string into a URL (fails on invalid URLs) and encodes back to
url.href.
import { Schema } from "effect"
const url = Schema.decodeUnknownSync(Schema.URLFromString)("https://effect.website/docs") // => URLSchema.encodeUnknownSync(Schema.URLFromString)(url) // => "https://effect.website/docs"JSON and wire formats
Section titled “JSON and wire formats”fromJsonString
Section titled “fromJsonString”Parses a JSON string and decodes the result with the provided schema; encodes
by validating then JSON.stringify-ing.
import { Schema } from "effect"
const Ids = Schema.fromJsonString(Schema.Array(Schema.Number))Schema.decodeUnknownSync(Ids)("[1,2,3]") // => [1, 2, 3]Schema.encodeUnknownSync(Ids)([1, 2, 3]) // => "[1,2,3]"UnknownFromJsonString
Section titled “UnknownFromJsonString”fromJsonString(Schema.Unknown) — parse arbitrary JSON without a target shape.
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.UnknownFromJsonString)('{"ok":true}') // => { ok: true }Schema.encodeUnknownSync(Schema.UnknownFromJsonString)({ ok: true }) // => '{"ok":true}'fromFormData
Section titled “fromFormData”FormData ↔ a value of the given schema, using bracket-path keys
(user[name]) for nesting.
import { Schema } from "effect"
const Form = Schema.fromFormData(Schema.Struct({ name: Schema.String }))const fd = new FormData()fd.append("name", "Alice")Schema.decodeUnknownSync(Form)(fd) // => { name: "Alice" }fromURLSearchParams
Section titled “fromURLSearchParams”URLSearchParams ↔ a value of the given schema, also via bracket-path keys.
import { Schema } from "effect"
const Query = Schema.fromURLSearchParams(Schema.Struct({ page: Schema.String }))Schema.decodeUnknownSync(Query)(new URLSearchParams("page=2")) // => { page: "2" }Trim / Trimmed
Section titled “Trim / Trimmed”Trimmed validates that a string has no surrounding whitespace; Trim
decodes by trimming (encode is passthrough, so it does not round-trip).
import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.Trim)(" hi ") // => "hi"Schema.decodeUnknownSync(Schema.Trimmed)("hi") // => "hi"// Schema.decodeUnknownSync(Schema.Trimmed)(" hi ") -> throwsOption transformations
Section titled “Option transformations”Each turns a nullable/optional encoded shape into an Option<T> on the decoded
side. They take the inner schema and return a decodeTo of Option<inner>.
OptionFromNullOr
Section titled “OptionFromNullOr”T | null ↔ Option<T>.
import { Schema } from "effect"
const schema = Schema.OptionFromNullOr(Schema.String)Schema.decodeUnknownSync(schema)(null) // => Option.none()Schema.encodeUnknownSync(schema)(Schema.decodeUnknownSync(schema)("a")) // => "a"OptionFromUndefinedOr
Section titled “OptionFromUndefinedOr”T | undefined ↔ Option<T>.
import { Schema } from "effect"
const schema = Schema.OptionFromUndefinedOr(Schema.Number)Schema.decodeUnknownSync(schema)(undefined) // => Option.none()Schema.decodeUnknownSync(schema)(5) // => Option.some(5)OptionFromNullishOr
Section titled “OptionFromNullishOr”T | null | undefined ↔ Option<T>. Choose how None encodes via
onNoneEncoding.
import { Schema } from "effect"
const schema = Schema.OptionFromNullishOr(Schema.String, { onNoneEncoding: null })Schema.decodeUnknownSync(schema)(null) // => Option.none()Schema.decodeUnknownSync(schema)("a") // => Option.some("a")OptionFromOptional
Section titled “OptionFromOptional”An optional (possibly-undefined) field ↔ Option<T>.
import { Schema } from "effect"
const schema = Schema.Struct({ age: Schema.OptionFromOptional(Schema.Number)})Schema.decodeUnknownSync(schema)({}) // => { age: Option.none() }Schema.decodeUnknownSync(schema)({ age: 30 }) // => { age: Option.some(30) }OptionFromOptionalKey
Section titled “OptionFromOptionalKey”An optional struct key ↔ Option<T>; absent key decodes to Option.none(),
and Option.none() encodes by omitting the key.
import { Schema } from "effect"
const schema = Schema.Struct({ name: Schema.OptionFromOptionalKey(Schema.String)})Schema.decodeUnknownSync(schema)({}) // => { name: Option.none() }Schema.decodeUnknownSync(schema)({ name: "Al" }) // => { name: Option.some("Al") }OptionFromOptionalNullOr
Section titled “OptionFromOptionalNullOr”An optional key whose value may also be null ↔ Option<T>.
import { Schema } from "effect"
const schema = Schema.Struct({ bio: Schema.OptionFromOptionalNullOr(Schema.String)})Schema.decodeUnknownSync(schema)({ bio: null }) // => { bio: Option.none() }Schema.decodeUnknownSync(schema)({}) // => { bio: Option.none() }Schema.decodeUnknownSync(schema)({ bio: "hi" }) // => { bio: Option.some("hi") }Redacted
Section titled “Redacted”Redacted
Section titled “Redacted”Wraps a schema so its value is hidden from error output and inspection. Expects
the input to already be a Redacted instance.
import { Redacted, Schema } from "effect"
const Secret = Schema.Redacted(Schema.String)Schema.decodeUnknownSync(Secret)(Redacted.make("p@ss")) // => Redacted(<redacted>)RedactedFromValue
Section titled “RedactedFromValue”Decodes the raw value and wraps it in Redacted<T> (encode unwraps, unless
disallowEncode is set).
import { Schema } from "effect"
const Secret = Schema.RedactedFromValue(Schema.String)const r = Schema.decodeUnknownSync(Secret)("p@ss") // => Redacted(<redacted>)Schema.encodeUnknownSync(Secret)(r) // => "p@ss"redact
Section titled “redact”The decoding middleware behind Redacted — wraps decode errors so schema details
do not leak in messages. Apply it to any schema.
import { Schema } from "effect"
const SafeNumber = Schema.redact(Schema.Number) // errors are redacted on failureStructural transforms
Section titled “Structural transforms”Swaps Type and Encoded, turning a decoder into its inverse. Calling flip
twice returns the original.
import { Schema } from "effect"
const StringFromNumber = Schema.flip(Schema.NumberFromString)Schema.decodeUnknownSync(StringFromNumber)(42) // => "42"Schema.encodeUnknownSync(StringFromNumber)("42") // => 42toType / toEncoded
Section titled “toType / toEncoded”toType collapses a schema to just its decoded (Type) side; toEncoded
collapses it to its Encoded side — both drop the transformation.
import { Schema } from "effect"
const TypeOnly = Schema.toType(Schema.NumberFromString) // schema for `number`const EncodedOnly = Schema.toEncoded(Schema.NumberFromString) // schema for `string`Schema.decodeUnknownSync(TypeOnly)(1) // => 1Schema.decodeUnknownSync(EncodedOnly)("1") // => "1"encodeKeys
Section titled “encodeKeys”Renames struct keys on the encoded side, keeping the decoded property names.
import { Schema } from "effect"
const Person = Schema.Struct({ firstName: Schema.String}).pipe(Schema.encodeKeys({ firstName: "first_name" }))
Schema.decodeUnknownSync(Person)({ first_name: "Al" }) // => { firstName: "Al" }Schema.encodeUnknownSync(Person)({ firstName: "Al" }) // => { first_name: "Al" }mutable
Section titled “mutable”Drops readonly from a struct/array/record’s decoded type (the runtime value is
unchanged).
import { Schema } from "effect"
const Items = Schema.mutable(Schema.Array(Schema.Number)) // Type: number[] (not readonly)Schema.decodeUnknownSync(Items)([1, 2]) // => [1, 2]Used inside custom declare schemas to attach a transformation to a target type
(this is how built-ins like Redacted define their JSON codec). Reach for
decodeTo/encodeTo in everyday code; use link when authoring a declaration
schema’s serializer.
import { Schema, SchemaGetter } from "effect"
// Sketch: link a target type to a transformation pair.const toCodec = Schema.link<string>()(Schema.String, { decode: SchemaGetter.passthrough(), encode: SchemaGetter.passthrough()})