Skip to content

Transformations

A transformation connects two schemas with a pair of functions — one that runs while decoding (EncodedType) and one that runs while encoding (TypeEncoded). 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.

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 -> string
const NumberFromString = Schema.String.pipe(
Schema.decodeTo(Schema.Number, {
decode: SchemaGetter.Number(),
encode: SchemaGetter.String()
})
)
console.log(Schema.decodeUnknownSync(NumberFromString)("42")) // => 42
console.log(Schema.encodeUnknownSync(NumberFromString)(42)) // => "42"

Schema.decode is a convenience for transformations where the encoded and decoded types are the same (e.g. stringstring). 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"

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"

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.14
console.log(Schema.encodeUnknownSync(NumberFromString)(3.14)) // => "3.14"

Schema.encode is the Type === Encoded convenience that mirrors Schema.decode.

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 both
const 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))
})

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)) // => 100
console.log(Schema.encodeUnknownSync(CentsFromDollars)(100)) // => 1

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))
})

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

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?)stringRecord<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" }

Type guard: true if a value is a Transformation instance.

import { SchemaTransformation } from "effect"
SchemaTransformation.isTransformation(SchemaTransformation.trim()) // => true
SchemaTransformation.isTransformation({ decode: null }) // => false

Middleware: 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")))
)
)

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.

Always produces a constant, ignoring the input.

import { SchemaGetter } from "effect"
const alwaysZero = SchemaGetter.succeed(0) // => Some(0) for any input

Always 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" })
)

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

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 3

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)
})

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 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 absent

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>() // => None

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" => 42
SchemaGetter.Boolean<string>() // Getter<boolean, string> e.g. "" => false
SchemaGetter.BigInt<string>() // Getter<bigint, string> e.g. "9" => 9n
SchemaGetter.Date<string>() // Getter<Date, string> e.g. "2020-01-01" => Date
  • 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"

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"

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>

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"] } }

These ship ready to use, so you rarely write getters by hand. Every entry below shows both directions with inline // => results.

stringnumber, allowing non-finite results like NaN / Infinity.

import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.NumberFromString)("3.14") // => 3.14
Schema.encodeUnknownSync(Schema.NumberFromString)(3.14) // => "3.14"

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

stringbigint. Decoding accepts only strings matching ^-?\d+$.

import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.BigIntFromString)("9007199254740993") // => 9007199254740993n
Schema.encodeUnknownSync(Schema.BigIntFromString)(9007199254740993n) // => "9007199254740993"

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

stringBigDecimal for arbitrary-precision decimals. (v4-new)

import { Schema } from "effect"
const bd = Schema.decodeUnknownSync(Schema.BigDecimalFromString)("1.5") // => BigDecimal 1.5
Schema.encodeUnknownSync(Schema.BigDecimalFromString)(bd) // => "1.5"

0 | 1boolean.

import { Schema } from "effect"
Schema.decodeUnknownSync(Schema.BooleanFromBit)(1) // => true
Schema.encodeUnknownSync(Schema.BooleanFromBit)(false) // => 0

stringDate (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") // => Date
Schema.encodeUnknownSync(Schema.DateFromString)(new Date(0)) // => "1970-01-01T00:00:00.000Z"

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.Utc

string (ISO) ↔ DateTime.Utc; encodes with DateTime.formatIso.

import { Schema } from "effect"
const dt = Schema.decodeUnknownSync(Schema.DateTimeUtcFromString)("2020-01-01T00:00:00Z") // => DateTime.Utc
Schema.encodeUnknownSync(Schema.DateTimeUtcFromString)(dt) // => "2020-01-01T00:00:00.000Z"

DateDateTime.Utc.

import { Schema } from "effect"
const dt = Schema.decodeUnknownSync(Schema.DateTimeUtcFromDate)(new Date(0)) // => DateTime.Utc
Schema.encodeUnknownSync(Schema.DateTimeUtcFromDate)(dt) // => Date(1970-01-01T00:00:00.000Z)

number (epoch millis) ↔ DateTime.Utc.

import { Schema } from "effect"
const dt = Schema.decodeUnknownSync(Schema.DateTimeUtcFromMillis)(0) // => DateTime.Utc (epoch)
Schema.encodeUnknownSync(Schema.DateTimeUtcFromMillis)(dt) // => 0

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.DateTimeZoned

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.Zoned
Schema.encodeUnknownSync(Schema.DateTimeZonedFromString)(z)
// => "2020-01-01T00:00:00.000Z[Europe/London]"

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") // => TimeZone
Schema.encodeUnknownSync(Schema.TimeZoneFromString)(tz) // => "Europe/London"

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") // => Named
Schema.encodeUnknownSync(Schema.TimeZoneNamedFromString)(named) // => "America/New_York"

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 values

Declaration schema for Duration values (no conversion; validates a Duration).

import { Duration, Schema } from "effect"
Schema.decodeUnknownSync(Schema.Duration)(Duration.seconds(5)) // => Duration(5s)

Human-readable stringDuration (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"

number (millis) ↔ Duration.

import { Schema } from "effect"
const d = Schema.decodeUnknownSync(Schema.DurationFromMillis)(2000) // => Duration(2s)
Schema.encodeUnknownSync(Schema.DurationFromMillis)(d) // => 2000

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) // => 2000000000n

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"

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 stringUint8Array 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 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") // => URL
Schema.encodeUnknownSync(Schema.URLFromString)(url) // => "https://effect.website/docs"

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]"

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}'

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" }

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" }

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

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

T | nullOption<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"

T | undefinedOption<T>.

import { Schema } from "effect"
const schema = Schema.OptionFromUndefinedOr(Schema.Number)
Schema.decodeUnknownSync(schema)(undefined) // => Option.none()
Schema.decodeUnknownSync(schema)(5) // => Option.some(5)

T | null | undefinedOption<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")

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) }

An optional struct keyOption<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") }

An optional key whose value may also be nullOption<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") }

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

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"

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 failure

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

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) // => 1
Schema.decodeUnknownSync(EncodedOnly)("1") // => "1"

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" }

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()
})