Documentation Index
Fetch the complete documentation index at: https://mintlify.com/effect-TS/effect-smol/llms.txt
Use this file to discover all available pages before exploring further.
Overview
Schema has undergone significant restructuring in v4. This guide maps v3 Schema APIs to their v4 equivalents, organized by migration complexity.
Migration Types
- rename — simple find-and-replace, safe to auto-apply
- variadic-to-array — convert variadic arguments to array syntax
- restructure — follows a clear pattern but needs structural changes
- manual — requires case-by-case decisions
- removed — no v4 equivalent
Quick Reference Table
| v3 API | v4 API | Type |
|---|
asSchema(schema) | revealCodec(schema) | rename |
encodedSchema(schema) | toEncoded(schema) | rename |
typeSchema(schema) | toType(schema) | rename |
compose(schemaB) | decodeTo(schemaB) | rename |
annotations(ann) | annotate(ann) | rename |
decodingFallback annotation | catchDecoding(...) | rename |
parseJson() | UnknownFromJsonString | rename |
parseJson(schema) | fromJsonString(schema) | rename |
pattern(regex) | check(isPattern(regex)) | rename |
nonEmptyString | isNonEmpty | rename |
BigIntFromSelf | BigInt | rename |
SymbolFromSelf | Symbol | rename |
URLFromSelf | URL | rename |
decodeUnknown | decodeUnknownEffect | rename |
decode | decodeEffect | rename |
Literal(null) | Null | restructure |
Literal("a", "b") | Literals(["a", "b"]) | variadic-to-array |
Union(A, B) | Union([A, B]) | variadic-to-array |
Tuple(A, B) | Tuple([A, B]) | variadic-to-array |
Record({ key, value }) | Record(key, value) | restructure |
filter(predicate) | check(makeFilter(predicate)) | restructure |
UUID | String.check(isUUID()) | restructure |
pick("a") | mapFields(Struct.pick(["a"])) | restructure |
omit("a") | mapFields(Struct.omit(["a"])) | restructure |
partial | mapFields(Struct.map(Schema.optional)) | restructure |
extend(structB) | mapFields(Struct.assign(fieldsB)) | restructure |
validate* | removed (use decode* + toType) | removed |
Simple Renames
Core API Renames
v3
import { Schema } from "effect"
const encoded = Schema.encodedSchema(mySchema)
const type = Schema.typeSchema(mySchema)
const revealed = Schema.asSchema(mySchema)
const composed = mySchema.pipe(Schema.compose(otherSchema))
const annotated = mySchema.pipe(Schema.annotations({ title: "My Schema" }))
v4
import { Schema } from "effect"
const encoded = Schema.toEncoded(mySchema)
const type = Schema.toType(mySchema)
const revealed = Schema.revealCodec(mySchema)
const composed = mySchema.pipe(Schema.decodeTo(otherSchema))
const annotated = mySchema.pipe(Schema.annotate({ title: "My Schema" }))
Decode/Encode Renames
All decode and encode functions have been renamed with more explicit names:
v3
import { Schema } from "effect"
const result1 = Schema.decodeUnknown(schema)(input)
const result2 = Schema.decode(schema)(input)
const result3 = Schema.decodeUnknownEither(schema)(input)
const result4 = Schema.decodeEither(schema)(input)
v4
import { Schema } from "effect"
const result1 = Schema.decodeUnknownEffect(schema)(input)
const result2 = Schema.decodeEffect(schema)(input)
const result3 = Schema.decodeUnknownExit(schema)(input)
const result4 = Schema.decodeExit(schema)(input)
Note the suffix change: Either → Exit to align with Effect’s terminology.
*FromSelf Suffix Removal
All *FromSelf schemas have been renamed to drop the suffix:
v3
import { Schema } from "effect"
const schemas = [
Schema.BigIntFromSelf,
Schema.SymbolFromSelf,
Schema.URLFromSelf,
Schema.DateFromSelf,
Schema.OptionFromSelf(Schema.String),
Schema.EitherFromSelf({ left: Schema.String, right: Schema.Number })
]
v4
import { Schema } from "effect"
const schemas = [
Schema.BigInt,
Schema.Symbol,
Schema.URL,
Schema.Date,
Schema.Option(Schema.String),
Schema.Result({ left: Schema.String, right: Schema.Number })
]
Also note: Either has been renamed to Result to align with v4 terminology.
Variadic to Array
Many constructors that accepted variadic arguments now require arrays:
Literals
v3
import { Schema } from "effect"
const schema = Schema.Literal("a", "b", "c")
const picked = Schema.Literal("a", "b", "c").pipe(Schema.pickLiteral("a", "b"))
v4
import { Schema } from "effect"
const schema = Schema.Literals(["a", "b", "c"])
const picked = Schema.Literals(["a", "b", "c"]).pick(["a", "b"])
Literal(null) should be replaced with the built-in Null schema.
Union and Tuple
v3
import { Schema } from "effect"
const union = Schema.Union(
Schema.String,
Schema.Number,
Schema.Boolean
)
const tuple = Schema.Tuple(
Schema.String,
Schema.Number
)
v4
import { Schema } from "effect"
const union = Schema.Union([
Schema.String,
Schema.Number,
Schema.Boolean
])
const tuple = Schema.Tuple([
Schema.String,
Schema.Number
])
TemplateLiteral
v3
import { Schema } from "effect"
const schema = Schema.TemplateLiteral(Schema.String, ".", Schema.String)
const parser = Schema.TemplateLiteralParser(Schema.String, ".", Schema.String)
v4
import { Schema } from "effect"
const schema = Schema.TemplateLiteral([Schema.String, ".", Schema.String])
// Use the `parts` property instead of repeating the template parts
const parser = Schema.TemplateLiteralParser(schema.parts)
Restructured APIs
Record
v3
import { Schema } from "effect"
const schema = Schema.Record({ key: Schema.String, value: Schema.Number })
v4
import { Schema } from "effect"
const schema = Schema.Record(Schema.String, Schema.Number)
pick / omit
Requires importing Struct from effect.
v3
import { Schema } from "effect"
const picked = Schema.Struct({ a: Schema.String, b: Schema.Number }).pipe(
Schema.pick("a")
)
const omitted = Schema.Struct({ a: Schema.String, b: Schema.Number }).pipe(
Schema.omit("b")
)
v4
import { Schema, Struct } from "effect"
const picked = Schema.Struct({ a: Schema.String, b: Schema.Number })
.mapFields(Struct.pick(["a"]))
const omitted = Schema.Struct({ a: Schema.String, b: Schema.Number })
.mapFields(Struct.omit(["b"]))
partial / required
v3
import { Schema } from "effect"
const struct = Schema.Struct({ a: Schema.String, b: Schema.Number })
const partial = struct.pipe(Schema.partial)
const exact = struct.pipe(Schema.partialWith({ exact: true }))
const required = partial.pipe(Schema.required)
v4
import { Schema, Struct } from "effect"
const struct = Schema.Struct({ a: Schema.String, b: Schema.Number })
// Allows undefined
const partial = struct.mapFields(Struct.map(Schema.optional))
// Exact optional (no undefined)
const exact = struct.mapFields(Struct.map(Schema.optionalKey))
// Make all fields required
const required = partial.mapFields(Struct.map(Schema.requiredKey))
// Make subset of fields partial
const mixedPartial = struct.mapFields(
Struct.mapPick(["a"], Schema.optional)
)
extend
v3
import { Schema } from "effect"
const extended = Schema.Struct({
a: Schema.String,
b: Schema.Number
}).pipe(
Schema.extend(Schema.Struct({ c: Schema.Boolean }))
)
v4
import { Schema, Struct } from "effect"
// Option 1: mapFields + Struct.assign
const extended = Schema.Struct({
a: Schema.String,
b: Schema.Number
}).mapFields(Struct.assign({ c: Schema.Boolean }))
// Option 2: fieldsAssign (more succinct)
const extended2 = Schema.Struct({
a: Schema.String,
b: Schema.Number
}).pipe(Schema.fieldsAssign({ c: Schema.Boolean }))
filter
v3
import { Schema } from "effect"
// Inline filter
const nonEmpty = Schema.String.pipe(
Schema.filter((s) => s.length > 0)
)
// Refinement
const some = Schema.Option(Schema.String).pipe(
Schema.filter(Option.isSome)
)
v4
import { Option, Schema } from "effect"
// Inline filter
const nonEmpty = Schema.String.check(
Schema.makeFilter((s) => s.length > 0)
)
// Refinement
const some = Schema.Option(Schema.String).pipe(
Schema.refine(Option.isSome)
)
Filter Predicates
All filter predicates have been renamed with is prefix and now use check(...):
v3
import { Schema } from "effect"
const schemas = [
Schema.String.pipe(Schema.pattern(/^\d+$/)),
Schema.String.pipe(Schema.nonEmptyString),
Schema.String.pipe(Schema.UUID),
Schema.String.pipe(Schema.ULID),
Schema.Number.pipe(Schema.greaterThan(0)),
Schema.Number.pipe(Schema.int),
Schema.Number.pipe(Schema.positive)
]
v4
import { Schema } from "effect"
const schemas = [
Schema.String.check(Schema.isPattern(/^\d+$/)),
Schema.String.check(Schema.isNonEmpty()),
Schema.String.check(Schema.isUUID()),
Schema.String.check(Schema.isULID()),
Schema.Number.check(Schema.isGreaterThan(0)),
Schema.Number.check(Schema.isInt())
// Note: positive/negative/nonNegative/nonPositive removed in v4
]
v3
import { Schema } from "effect"
const BoolFromString = Schema.transform(
Schema.Literal("on", "off"),
Schema.Boolean,
{
strict: true,
decode: (literal) => literal === "on",
encode: (bool) => (bool ? "on" : "off")
}
)
v4
import { Schema, SchemaTransformation } from "effect"
const BoolFromString = Schema.Literals(["on", "off"]).pipe(
Schema.decodeTo(
Schema.Boolean,
SchemaTransformation.transform({
decode: (literal) => literal === "on",
encode: (bool) => (bool ? "on" : "off")
})
)
)
Requires importing SchemaTransformation from effect.
transformOrFail follows a similar pattern:
v3
import { ParseResult, Schema } from "effect"
const NumberFromString = Schema.transformOrFail(
Schema.String,
Schema.Number,
{
strict: true,
decode: (input, _, ast) => {
const parsed = parseFloat(input)
if (isNaN(parsed)) {
return ParseResult.fail(
new ParseResult.Type(ast, input, "Failed to convert")
)
}
return ParseResult.succeed(parsed)
},
encode: (input) => ParseResult.succeed(input.toString())
}
)
v4
import { Effect, Number, Schema, SchemaGetter, SchemaIssue } from "effect"
const NumberFromString = Schema.String.pipe(
Schema.decodeTo(Schema.Number, {
decode: SchemaGetter.transformOrFail((s) => {
const n = Number.parse(s)
if (n === undefined) {
return Effect.fail(new SchemaIssue.InvalidValue(Option.some(s)))
}
return Effect.succeed(n)
}),
encode: SchemaGetter.String()
})
)
decodingFallback
v3
import { Effect, Schema } from "effect"
const schema = Schema.String.annotations({
decodingFallback: () => Effect.succeed("a")
})
v4
import { Effect, Schema } from "effect"
const schema = Schema.String.pipe(
Schema.catchDecoding(() => Effect.succeedSome("a"))
)
Manual Migrations
optionalWith
The optionalWith API has been split into multiple explicit patterns based on the options used:
| v3 options | v4 pattern |
|---|
{ exact: true } | optionalKey(schema) |
{ default } | optional(schema) + decodeTo + withDefault(...) |
{ exact: true, default } | optionalKey(schema) + decodeTo + withDefault(...) |
{ nullable: true } | optional(NullOr(schema)) + filter null |
{ nullable: true, default } | optional(NullOr(schema)) + filter null + orElseSome |
Example: { exact: true } (simplest case)
v3
import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.optionalWith(Schema.NumberFromString, { exact: true })
})
v4
import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.optionalKey(Schema.NumberFromString)
})
Example: { nullable: true, exact: true, default } (most complex case)
v3
import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.optionalWith(Schema.NumberFromString, {
nullable: true,
default: () => -1,
exact: true
})
})
v4
import { Option, Predicate, Schema, SchemaGetter } from "effect"
const schema = Schema.Struct({
a: Schema.optionalKey(Schema.NullOr(Schema.NumberFromString)).pipe(
Schema.decodeTo(Schema.toType(Schema.NumberFromString), {
decode: SchemaGetter.transformOptional((o) =>
o.pipe(
Option.filter(Predicate.isNotNull),
Option.orElseSome(() => -1)
)
),
encode: SchemaGetter.required()
})
)
})
optionalToOptional, optionalToRequired, and requiredToOptional are replaced by Schema.decodeTo + SchemaGetter.transformOptional.
Example: optionalToRequired (setting default for missing field)
v3
import { Option, Schema } from "effect"
const schema = Schema.Struct({
a: Schema.optionalToRequired(Schema.String, Schema.NullOr(Schema.String), {
decode: Option.getOrElse(() => null),
encode: Option.liftPredicate((value) => value !== null)
})
})
v4
import { Option, Schema, SchemaGetter } from "effect"
const schema = Schema.Struct({
a: Schema.optionalKey(Schema.String).pipe(
Schema.decodeTo(Schema.NullOr(Schema.String), {
decode: SchemaGetter.transformOptional(
Option.orElseSome(() => null)
),
encode: SchemaGetter.transformOptional(
Option.filter((value) => value !== null)
)
})
)
})
filterEffect
v3
import { Effect, Schema } from "effect"
async function validateUsername(username: string) {
return Promise.resolve(username === "gcanti")
}
const ValidUsername = Schema.String.pipe(
Schema.filterEffect((username) =>
Effect.promise(() =>
validateUsername(username).then((valid) => valid || "Invalid username")
)
)
)
v4
import { Effect, Schema, SchemaGetter } from "effect"
async function validateUsername(username: string) {
return Promise.resolve(username === "gcanti")
}
const ValidUsername = Schema.String.pipe(
Schema.decode({
decode: SchemaGetter.checkEffect((username) =>
Effect.promise(() =>
validateUsername(username).then((valid) => valid || "Invalid username")
)
),
encode: SchemaGetter.passthrough()
})
)
rename (experimental)
v3
import { Schema } from "effect"
const schema = Schema.Struct({
a: Schema.String,
b: Schema.Number
}).pipe(Schema.rename({ a: "c" }))
v4
import { Schema } from "effect"
// experimental API
const schema = Schema.Struct({
a: Schema.String,
b: Schema.Number
}).pipe(Schema.encodeKeys({ a: "c" }))
Removed APIs
validate* APIs
The validate, validateEither, validatePromise, validateSync, and validateOption APIs have been removed. Use Schema.decode* + Schema.toType instead.
v3
import { Schema } from "effect"
const validate = Schema.validateSync(Schema.String)
const result = validate(input)
v4
import { Schema } from "effect"
const validate = Schema.decodeSync(Schema.toType(Schema.String))
const result = validate(input)
Other Removed APIs
keyof — removed without direct replacement
ArrayEnsure — removed
NonEmptyArrayEnsure — removed
withDefaults — removed
fromKey — removed
forkAll — removed
forkWithErrorHandler — removed
Capitalize / Lowercase / Uppercase
v3
import { Schema } from "effect"
const schema = Schema.Capitalize
v4
import { Schema, SchemaTransformation } from "effect"
const schema = Schema.String.pipe(
Schema.decodeTo(
Schema.String.check(Schema.isCapitalized()),
SchemaTransformation.capitalize()
)
)
NonEmptyTrimmedString
v3
import { Schema } from "effect"
const schema = Schema.NonEmptyTrimmedString
v4
import { Schema } from "effect"
const schema = Schema.Trimmed.check(Schema.isNonEmpty())
Migration Checklist