Filters
A plain predicate answers one question — does this value pass? — and throws
away everything else. When it returns false you are left holding the original
value with no record of why it was rejected, and TypeScript has not learned
anything new about the values that did pass.
The Filter module replaces that boolean with data. A Filter<Input, Pass, Fail>
is a function (input: Input) => Result<Pass, Fail>: success means the value
passed (and may have been narrowed or transformed along the way),
failure means it was filtered out — and the rejected value is carried in the
Result failure channel as ordinary data, not an exception.
import { Filter, Predicate, Result } from "effect"
// A plain predicate: returns a boolean, the rejected value vanishesconst isPositivePredicate = (n: number) => n > 0isPositivePredicate(5) // => trueisPositivePredicate(-3) // => false (where did -3 go?)
// The same rule as a Filter: the rejected value survives as dataconst isPositive = Filter.fromPredicate((n: number) => n > 0)isPositive(5) // => Result.succeed(5)isPositive(-3) // => Result.fail(-3) the rejected value is preservedBecause the rejected value is a Result.fail rather than a thrown error,
filters compose with combinators (or, zip, compose, …) the same way
Result and Option do, instead of forcing you into try/catch.
A refinement predicate makes the win clearer — the passing value is narrowed:
import { Filter, Result } from "effect"
// Refinement: narrows unknown -> string on the pass sideconst onlyStrings = Filter.fromPredicate( (x: unknown): x is string => typeof x === "string")
onlyStrings("hello") // => Result.succeed("hello") typed as Result<string, unknown>onlyStrings(42) // => Result.fail(42)The effectful form
Section titled “The effectful form”When the decision needs async work, can error, or depends on services, use
FilterEffect — the function returns an Effect<Result<Pass, Fail>, E, R>
instead of a bare Result.
import { Effect, Filter, Result } from "effect"
const validateId: Filter.FilterEffect<string, string, string> = (id) => Effect.gen(function* () { const ok = yield* Effect.succeed(id.length > 0) return ok ? Result.succeed(id) : Result.fail(id) })Reference
Section titled “Reference”Models
Section titled “Models”Filter
Section titled “Filter”Filter<Input, Pass = Input, Fail = Input> is a function (input) => Result<Pass, Fail>.
Success carries the (possibly narrowed/transformed) passing value; failure
carries the rejected value.
import { Filter, Result } from "effect"
const positive: Filter.Filter<number> = (n) => n > 0 ? Result.succeed(n) : Result.fail(n)
positive(5) // => Result.succeed(5)positive(-3) // => Result.fail(-3)FilterEffect
Section titled “FilterEffect”FilterEffect<Input, Pass, Fail, E = never, R = never> is the effectful
counterpart: (input) => Effect<Result<Pass, Fail>, E, R>. Use it when the
filter performs effects, can fail with E, or needs services R.
import { Effect, Filter, Result } from "effect"
const nonEmpty: Filter.FilterEffect<string, string, string> = (s) => Effect.sync(() => (s.length > 0 ? Result.succeed(s) : Result.fail(s)))Constructors
Section titled “Constructors”Builds a Filter from a function returning Result.succeed(pass) /
Result.fail(fail). The primary way to write a custom filter, including ones
that transform the passing value.
import { Filter, Result } from "effect"
const upperIfNonEmpty = Filter.make((s: string) => s.length > 0 ? Result.succeed(s.toUpperCase()) : Result.fail(s))
upperIfNonEmpty("hi") // => Result.succeed("HI")upperIfNonEmpty("") // => Result.fail("")makeEffect
Section titled “makeEffect”Builds a FilterEffect from a function returning an Effect<Result<...>>. Use
for async checks, error handling, or service access.
import { Effect, Filter, Result } from "effect"
const validate = Filter.makeEffect((id: string) => Effect.gen(function* () { const ok = yield* Effect.succeed(id.length > 0) return ok ? Result.succeed(id) : Result.fail(id) }))
Effect.runSync(validate("abc")) // => Result.succeed("abc")Effect.runSync(validate("")) // => Result.fail("")fromPredicate
Section titled “fromPredicate”Turns a predicate into a filter that passes the input unchanged on true and
fails with the input on false. When given a refinement (x is B), the
pass side is narrowed to B.
import { Filter } from "effect"
// Plain predicate: Filter<number>const positive = Filter.fromPredicate((n: number) => n > 0)positive(5) // => Result.succeed(5)positive(-1) // => Result.fail(-1)
// Refinement: narrows the pass type to stringconst str = Filter.fromPredicate((x: unknown): x is string => typeof x === "string")str("ok") // => Result.succeed("ok") pass typed as stringstr(1) // => Result.fail(1)fromPredicateOption
Section titled “fromPredicateOption”Builds a filter from a function returning an Option. Some(value) passes with
value (which can be a transformed result); None fails with the original
input.
import { Filter, Option } from "effect"
const parsePort = Filter.fromPredicateOption((s: string) => { const n = Number(s) return Number.isInteger(n) && n > 0 && n < 65536 ? Option.some(n) : Option.none()})
parsePort("8080") // => Result.succeed(8080)parsePort("nope") // => Result.fail("nope") fails with the original stringBuilds a filter that runs a function and passes its return value, failing with the original input if the function throws.
import { Filter } from "effect"
const parseJson = Filter.try((s: string) => JSON.parse(s) as unknown)
parseJson('{"a":1}') // => Result.succeed({ a: 1 })parseJson("{bad}") // => Result.fail("{bad}") the original string, not the thrown errorNarrowing filters (unknown → T)
Section titled “Narrowing filters (unknown → T)”Predefined filters that accept an unknown and narrow it to a primitive type on
the pass side, failing with the original input otherwise.
string
Section titled “string”Passes string values.
import { Filter } from "effect"
Filter.string("hello") // => Result.succeed("hello")Filter.string(42) // => Result.fail(42)number
Section titled “number”Passes number values.
import { Filter } from "effect"
Filter.number(42) // => Result.succeed(42)Filter.number("42") // => Result.fail("42")boolean
Section titled “boolean”Passes boolean values (true and false).
import { Filter } from "effect"
Filter.boolean(false) // => Result.succeed(false)Filter.boolean(0) // => Result.fail(0)bigint
Section titled “bigint”Passes bigint primitives. Does not coerce: 1n passes, 1 fails.
import { Filter } from "effect"
Filter.bigint(1n) // => Result.succeed(1n)Filter.bigint(1) // => Result.fail(1)symbol
Section titled “symbol”Passes symbol values.
import { Filter } from "effect"
const sym = Symbol("id")Filter.symbol(sym) // => Result.succeed(sym)Filter.symbol("id") // => Result.fail("id")Passes Date instances. Uses instanceof Date, so an invalid Date still
passes — the timestamp is not validated.
import { Filter } from "effect"
const d = new Date("2026-05-30")Filter.date(d) // => Result.succeed(d)Filter.date("2026-05-30") // => Result.fail("2026-05-30")Shape & variant matching
Section titled “Shape & variant matching”instanceOf
Section titled “instanceOf”Passes inputs that are instanceof the given constructor, narrowing the pass
side to that instance type.
import { Filter } from "effect"
const isError = Filter.instanceOf(Error)
const e = new TypeError("boom")isError(e) // => Result.succeed(e) narrowed to ErrorisError("boom") // => Result.fail("boom")Passes inputs whose own has(key) method returns true for the given key.
Designed for Set, Map, and similar containers.
import { Filter } from "effect"
const hasUserId = Filter.has("user-1")const present = new Set(["user-1", "user-2"])const absent = new Set(["user-3"])
hasUserId(present) // => Result.succeed(present)hasUserId(absent) // => Result.fail(absent)tagged
Section titled “tagged”Passes only the matching member of a _tag-discriminated union, narrowing the
pass side to that variant (ExtractTag) and the fail side to the rest
(ExcludeTag). Only _tag is checked, not the other fields. Use the curried
tagged<Input>()(tag) form when you want to fix the input union up front.
import { Filter } from "effect"
type Event = | { readonly _tag: "Click"; readonly x: number } | { readonly _tag: "Key"; readonly code: string }
// Fix the input type, then pick a tag — pass narrows to the Click variantconst onClick = Filter.tagged<Event>()("Click")
onClick({ _tag: "Click", x: 10 }) // => Result.succeed({ _tag: "Click", x: 10 })onClick({ _tag: "Key", code: "Enter" }) // => Result.fail({ _tag: "Key", code: "Enter" })reason
Section titled “reason”Extracts a nested reason variant from a tagged error: passes when the input’s
_tag matches tag and its reason._tag matches reasonTag, succeeding
with the inner reason. Otherwise fails with the original input. See
Reason errors.
import { Filter } from "effect"
type AppError = { readonly _tag: "AppError" readonly reason: | { readonly _tag: "NotFound"; readonly id: string } | { readonly _tag: "Forbidden" }}
const notFound = Filter.reason<AppError>()("AppError", "NotFound")
notFound({ _tag: "AppError", reason: { _tag: "NotFound", id: "u1" } })// => Result.succeed({ _tag: "NotFound", id: "u1" })
notFound({ _tag: "AppError", reason: { _tag: "Forbidden" } })// => Result.fail({ _tag: "AppError", reason: { _tag: "Forbidden" } })Value matching
Section titled “Value matching”equals
Section titled “equals”Passes inputs structurally equal to the expected value, via
Equal.equals. Distinct objects with equal contents
pass.
import { Filter } from "effect"
const isOrigin = Filter.equals({ x: 0, y: 0 })
isOrigin({ x: 0, y: 0 }) // => Result.succeed({ x: 0, y: 0 }) different reference, equal contentsisOrigin({ x: 1, y: 0 }) // => Result.fail({ x: 1, y: 0 })equalsStrict
Section titled “equalsStrict”Passes inputs equal to the expected value via JavaScript ===. Objects pass
only when they are the same reference, and NaN never passes.
import { Filter } from "effect"
const target = { x: 0, y: 0 }const isTarget = Filter.equalsStrict(target)
isTarget(target) // => Result.succeed(target) same referenceisTarget({ x: 0, y: 0 }) // => Result.fail({ x: 0, y: 0 }) equal contents, different referenceCombinators — OR
Section titled “Combinators — OR”Tries self, then that; the first success wins. If self passes, its
result is returned; otherwise that is run on the same input.
import { Filter } from "effect"
// Accept either a string or a numberconst stringOrNumber = Filter.or(Filter.string, Filter.number)
stringOrNumber("hi") // => Result.succeed("hi")stringOrNumber(42) // => Result.succeed(42)stringOrNumber(true) // => Result.fail(true)Combinators — AND
Section titled “Combinators — AND”These run both filters on the same input; if either fails, the combination fails with that failure.
Both filters must pass; the passes are combined into a tuple [PassL, PassR].
import { Filter } from "effect"
const positive = Filter.fromPredicate((n: number) => n > 0)const even = Filter.fromPredicate((n: number) => n % 2 === 0)
const positiveEvenPair = Filter.zip(positive, even)
positiveEvenPair(4) // => Result.succeed([4, 4])positiveEvenPair(3) // => Result.fail(3)zipWith
Section titled “zipWith”Like zip, but combines the two passes with a function f instead of building
a tuple.
import { Filter } from "effect"
const str = Filter.stringconst positive = Filter.fromPredicate((n: number) => n > 0)
// Both filters run on the same input; here a label string passes both as-isconst labelled = Filter.zipWith(str, str, (a, b) => `${a}/${b}`)
labelled("x") // => Result.succeed("x/x")labelled(1) // => Result.fail(1)andLeft
Section titled “andLeft”Both filters must pass; keeps the left filter’s pass value.
import { Filter } from "effect"
const positive = Filter.fromPredicate((n: number) => n > 0)const even = Filter.fromPredicate((n: number) => n % 2 === 0)
const positiveAndEven = Filter.andLeft(positive, even)
positiveAndEven(4) // => Result.succeed(4) (left pass kept)positiveAndEven(3) // => Result.fail(3)andRight
Section titled “andRight”Both filters must pass; keeps the right filter’s pass value (useful when the right filter transforms).
import { Filter, Result } from "effect"
const positive = Filter.fromPredicate((n: number) => n > 0)const doubled = Filter.make((n: number) => n > 0 ? Result.succeed(n * 2) : Result.fail(n))
const positiveDoubled = Filter.andRight(positive, doubled)
positiveDoubled(5) // => Result.succeed(10) (right pass kept)positiveDoubled(-1) // => Result.fail(-1)Sequencing
Section titled “Sequencing”compose
Section titled “compose”Runs self, then feeds its pass value into that. The intermediate failure
value is preserved: if self fails you get self’s failure, if that fails you
get that’s failure.
import { Filter, Result } from "effect"
const nonEmptyUpper = Filter.make((s: string) => s.length > 0 ? Result.succeed(s.toUpperCase()) : Result.fail(s))
// unknown -> string -> uppercaseconst toUpper = Filter.compose(Filter.string, nonEmptyUpper)
toUpper("hi") // => Result.succeed("HI")toUpper("") // => Result.fail("") failure from the second filter (the intermediate "" string)toUpper(42) // => Result.fail(42) failure from the first filtercomposePassthrough
Section titled “composePassthrough”Like compose, but on any failure it fails with the original input rather
than the intermediate failure value.
import { Filter, Result } from "effect"
const nonEmptyUpper = Filter.make((s: string) => s.length > 0 ? Result.succeed(s.toUpperCase()) : Result.fail(s))
const toUpper = Filter.composePassthrough(Filter.string, nonEmptyUpper)
toUpper("hi") // => Result.succeed("HI")toUpper(42) // => Result.fail(42) original input, same as compose here// Difference: a value that passes the first filter but fails the secondtoUpper("") // => Result.fail("") the ORIGINAL input "", not an intermediate valueMapping
Section titled “Mapping”mapFail
Section titled “mapFail”Transforms the failure value only, leaving passes untouched. Dual, so it
works data-first or in pipe.
import { Filter } from "effect"
const positive = Filter.fromPredicate((n: number) => n > 0)
const withMessage = Filter.mapFail(positive, (n) => `rejected: ${n}`)
withMessage(5) // => Result.succeed(5)withMessage(-1) // => Result.fail("rejected: -1")Conversions
Section titled “Conversions”Adapt a Filter back into the plain shapes other APIs expect.
toPredicate
Section titled “toPredicate”Returns (a) => boolean — true when the filter passes, dropping both
payloads.
import { Filter } from "effect"
const isPositive = Filter.toPredicate(Filter.fromPredicate((n: number) => n > 0))
isPositive(5) // => trueisPositive(-1) // => false;[1, -2, 3].filter(isPositive) // => [1, 3]toOption
Section titled “toOption”Returns (a) => Option<Pass> — Some(pass) when the filter passes, None when
it fails (the failure value is discarded).
import { Filter } from "effect"
const asOption = Filter.toOption(Filter.string)
asOption("hi") // => Option.some("hi")asOption(42) // => Option.none()toResult
Section titled “toResult”Returns (a) => Result<Pass, Fail> — preserves both the pass and the failure
value. Effectively the identity view of the filter as a plain Result-returning
function.
import { Filter } from "effect"
const asResult = Filter.toResult(Filter.number)
asResult(42) // => Result.succeed(42)asResult("x") // => Result.fail("x")