Filters
A filter (or check) is a constraint attached to a schema that a value must
satisfy after it has the right type. Effect ships a large set of built-in
checks — length, range, pattern, format — and lets you write your own. Attach
them with .check(...).
import { Schema } from "effect"
// A registration form. Each field carries the constraints it must satisfy.const Registration = Schema.Struct({ // String must be non-empty and at most 50 characters. username: Schema.String.check( Schema.isMinLength(1), Schema.isMaxLength(50) ), // Number must be an integer in a valid age range. age: Schema.Number.check( Schema.isInt(), Schema.isBetween({ minimum: 13, maximum: 120 }) ), // String must match an email-ish pattern. email: Schema.String.check(Schema.isPattern(/^[^@\s]+@[^@\s]+$/))})
const user = Schema.decodeUnknownSync(Registration)({ username: "alice", age: 30, email: "alice@example.com"})
console.log(user.username) // "alice"Attaching checks
Section titled “Attaching checks”There are two equivalent ways to attach checks: the .check(...) method on a
schema, or the standalone Schema.check(...) combinator for use in a pipe. Both
accept one or more checks and run them in order.
import { Schema } from "effect"
// Method formconst A = Schema.Number.check( Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(120))
// Pipe form — identical resultconst B = Schema.Number.pipe( Schema.check( Schema.isGreaterThanOrEqualTo(0), Schema.isLessThanOrEqualTo(120) ))By default, when multiple checks fail only the first is reported. Pass
{ errors: "all" } to the decoding runner to collect
every failure.
Built-in checks
Section titled “Built-in checks”A representative selection — all live on the Schema namespace:
| Category | Checks |
| ----------- | -------------------------------------------------------------------------------------------- |
| Length/size | isMinLength, isMaxLength, isLengthBetween, isNonEmpty |
| Numbers | isGreaterThan, isGreaterThanOrEqualTo, isLessThan, isLessThanOrEqualTo, isBetween, isInt, isMultipleOf |
| Strings | isPattern, isStartsWith, isEndsWith, isUUID, isTrimmed |
import { Schema } from "effect"
const Port = Schema.Number.check(Schema.isBetween({ minimum: 1, maximum: 65535 }))const Slug = Schema.String.check(Schema.isPattern(/^[a-z0-9-]+$/))const Id = Schema.String.check(Schema.isUUID(4))Custom checks
Section titled “Custom checks”For one-off predicates, Schema.makeFilter builds a check from a function. The
predicate receives the decoded value and returns a FilterIssue describing the
failure (or undefined/true for success). You can point the failure at a
nested path and even report several issues at once.
import { Schema } from "effect"
// Cross-field validation: confirmPassword must match password.const PasswordChange = Schema.Struct({ password: Schema.String, confirmPassword: Schema.String}).check( Schema.makeFilter((o) => o.password === o.confirmPassword ? undefined // success : { path: ["confirmPassword"], issue: "passwords must match" } ))
console.log( String( Schema.decodeUnknownExit(PasswordChange)({ password: "hunter2", confirmPassword: "typo" }) ))// Failure(Cause([Fail(SchemaError: passwords must match// at ["confirmPassword"])]))Narrowing the type
Section titled “Narrowing the type”Schema.refine attaches a type-guard predicate that both validates at runtime
and narrows the decoded type. Use it when the constraint corresponds to a
more specific TypeScript type.
import { Schema } from "effect"
// Narrow `string` to the literal "GET" | "POST" with a guard.const Method = Schema.String.pipe( Schema.refine( (s): s is "GET" | "POST" => s === "GET" || s === "POST" ))
type Method = typeof Method.Type // "GET" | "POST"Branding
Section titled “Branding”Schema.brand gives a value a nominal type so structurally-identical values
cannot be mixed up — a UserId string can no longer be passed where an
OrderId string is expected. Branding narrows the type but adds no runtime
check on its own, so combine it with the checks that define the brand.
import { Schema } from "effect"
const UserId = Schema.String.pipe( Schema.check(Schema.isNonEmpty()), Schema.brand("UserId"))
type UserId = typeof UserId.Type // string & Brand<"UserId">
const id = Schema.decodeUnknownSync(UserId)("u_1") // typed as UserIdWith values validated, you can also convert between representations — covered in transformations.