Skip to content
Effect Days 2026 Get your ticket

Optic

Reads and updates focused parts of values without mutating the original value.

An optic describes where to look inside a value, such as a record field, a union variant, an optional value, or several values in a collection. Different optic types describe different kinds of focus: some always find a value, some may not, and some can find many. This module includes the optic types, constructors, focusing helpers, and operations for replacing, modifying, or collecting focused values.

24 exports Added in v4.0.0 Source

Constructors

entries

Added in v4.0.0 Source

Iso that converts a Record<string, A> to an array of [key, value] entries and back.

When to use

Use when you want to traverse or manipulate record entries as an array (e.g. with .forEach()).

Details

  • get uses Object.entries.
  • set uses Object.fromEntries.
  • Round-trip is lossless for Record<string, A>.

See

  • Iso — the type this function returns
  • id — identity iso

Signature

declare function entries<A>(): Iso<Record<string, A>, readonly Array<readonly [string, A]>>

Example

(Traversing record values)

import { Optic, Schema } from "effect"
const _positiveValues = Optic.entries<number>()
.forEach((entry) => entry.key(1).check(Schema.isGreaterThan(0)))
const inc = _positiveValues.modifyAll((n) => n + 1)
inc({ a: 0, b: 3, c: -1 }) // => { a: 0, b: 4, c: -1 }

failure

Added in v4.0.0 Source

Prism that focuses on the failure value of a Result.

When to use

Use when you have a Result<A, E> and want to read/update E only when it is a Failure.

Details

  • getResult fails when the result is a Success.
  • set(e) produces Result.fail(e).

See

  • success — focuses on the success side
  • Prism — the type this function returns

Signature

declare function failure<A, E>(): Prism<Result<A, E>, E>

Example

(Accessing failure)

import { Optic, Result } from "effect"
const _err = Optic.id<Result.Result<number, string>>().compose(Optic.failure())
_err.getResult(Result.fail("oops")) // => Result.succeed("oops")
Result.isFailure(_err.getResult(Result.succeed(42))) // => true

fromChecks

Added in v4.0.0 Source

Creates a Prism from one or more Schema validation checks.

When to use

Use when you want to narrow T to the subset that passes certain validation rules (e.g. positive integer).

  • You already have Schema.isGreaterThan, Schema.isInt, etc.

Details

  • getResult runs all checks and preserves their structured issues when any check fails.
  • set is identity — the value passes through unchanged.

See

  • makePrism — constructor with custom getter/setter
  • Prism — the type this function returns

Signature

declare function fromChecks<T>(...checks: readonly [Check<T>, Check<T>]): Prism<T, T>

Example

(Creating a positive integer prism)

import { Optic, Result, Schema } from "effect"
const posInt = Optic.fromChecks<number>(
Schema.isGreaterThan(0),
Schema.isInt()
)
posInt.getResult(3) // => Result.succeed(3)
Result.isFailure(posInt.getResult(-1)) // => true

id

Added in v4.0.0 Source

Iso that focuses on the whole value unchanged.

When to use

Use when you need to start an optic chain with a focus on the whole value.

Details

  • get(s) returns s.
  • set(a) returns a.
  • Singleton — every call returns the same instance.

See

  • Iso — the type this function returns

Signature

declare function id<S>(): Iso<S, S>

Example

(Starting an optic chain)

import { Optic } from "effect"
type S = { readonly x: number }
const _x = Optic.id<S>().key("x")
_x.get({ x: 42 }) // => 42

makeIso

Added in v4.0.0 Source

Creates an Iso from a pair of conversion functions.

When to use

Use when you have two pure conversion functions that preserve all information between S and A.

Details

The returned optic can be composed with any other optic.

See

  • Iso — the type this function returns
  • id — identity iso (no conversion)

Signature

declare function makeIso<S, A>(get: (s: S) => A, set: (a: A) => S): Iso<S, A>

Example

(Wrapping and unwrapping a branded type)

import { Optic } from "effect"
type Meters = { readonly value: number }
const meters = Optic.makeIso<Meters, number>(
(m) => m.value,
(n) => ({ value: n })
)
meters.get({ value: 100 }) // => 100
meters.set(42) // => { value: 42 }

makeLens

Added in v4.0.0 Source

Creates a Lens from a getter and a replacer.

When to use

Use when you can always extract A from S and produce a new S by substituting a new A.

Details

  • replace(a, s) should return a structurally new S with a in place of the old focus.

See

  • Lens — the type this function returns
  • makeIso — when no original S is needed for set

Signature

declare function makeLens<S, A>(get: (s: S) => A, replace: (a: A, s: S) => S): Lens<S, A>

Example

(Focusing on the first element of a pair)

import { Optic } from "effect"
const _first = Optic.makeLens<readonly [string, number], string>(
(pair) => pair[0],
(s, pair) => [s, pair[1]]
)
_first.get(["hello", 42]) // => "hello"
_first.replace("world", ["hello", 42]) // => ["world", 42]

makeOptional

Added in v4.0.0 Source

Creates an Optional from a fallible getter and a fallible setter.

When to use

Use when you need an optic for a focus that may be missing on read and may reject updates on write.

Details

  • getResult should return Result.fail(issue) on mismatch.
  • set should return Result.fail(issue) when the update cannot be applied.
  • Issues are not formatted automatically; callers choose how to render them.

See

  • Optional — the type this function returns
  • makeLens — when reading always succeeds
  • makePrism — when writing always succeeds

Signature

declare function makeOptional<S, A>(getResult: (s: S) => Result<A, Issue>, set: (a: A, s: S) => Result<S, Issue>): Optional<S, A>

Example

(Accessing record keys safely)

import { Optic, Result, SchemaIssue } from "effect"
const atKey = (key: string) => {
const issue = new SchemaIssue.Pointer([key], new SchemaIssue.MissingKey(undefined))
return Optic.makeOptional<Record<string, number>, number>(
(s) =>
Object.hasOwn(s, key)
? Result.succeed(s[key])
: Result.fail(issue),
(a, s) =>
Object.hasOwn(s, key)
? Result.succeed({ ...s, [key]: a })
: Result.fail(issue)
)
}
atKey("x").getResult({ x: 1 }) // => Result.succeed(1)

makePrism

Added in v4.0.0 Source

Creates a Prism from a fallible getter and an infallible setter.

When to use

Use when reading can fail (the part may not exist in S), but building S from A always succeeds.

Details

  • getResult should return Result.fail(issue) on mismatch.
  • Issues are not formatted automatically; callers choose how to render them.

See

  • Prism — the type this function returns
  • fromChecks — build from Schema checks instead

Signature

declare function makePrism<S, A>(getResult: (s: S) => Result<A, Issue>, set: (a: A) => S): Prism<S, A>

Example

(Parsing a string to a number)

import { Optic, Result, SchemaIssue } from "effect"
const numeric = Optic.makePrism<string, number>(
(s) => {
const n = Number(s)
return Number.isNaN(n)
? Result.fail(new SchemaIssue.InvalidValue({ message: "not a number" }))
: Result.succeed(n)
},
String
)
numeric.getResult("42") // => Result.succeed(42)
numeric.set(42) // => "42"

none

Added in v4.0.0 Source

Prism that focuses on Option.None, exposing undefined.

When to use

Use when you want to match or construct None values within an optic chain.

Details

  • getResult succeeds with undefined when the option is None.
  • getResult fails when the option is Some.
  • set(undefined) produces Option.none().

See

  • some — focuses on Some instead
  • Prism — the type this function returns

Signature

declare function none<A>(): Prism<Option<A>, undefined>

Example

(Matching None)

import { Optic, Option, Result } from "effect"
const _none = Optic.id<Option.Option<number>>().compose(Optic.none())
_none.getResult(Option.none()) // => Result.succeed(undefined)
Result.isFailure(_none.getResult(Option.some(1))) // => true

some

Added in v4.0.0 Source

Prism that focuses on the value inside Option.Some.

When to use

Use when you have an Option<A> and want to read/update the inner value only when it is Some.

Details

  • getResult fails with a structured issue when the option is None.
  • set(a) wraps a in Option.some(a).

See

  • none — focuses on None instead
  • Prism — the type this function returns

Signature

declare function some<A>(): Prism<Option<A>, A>

Example

(Accessing Some value)

import { Optic, Option, Result } from "effect"
const _some = Optic.id<Option.Option<number>>().compose(Optic.some())
_some.getResult(Option.some(42)) // => Result.succeed(42)
Result.isFailure(_some.getResult(Option.none())) // => true
_some.set(10) // => Option.some(10)

success

Added in v4.0.0 Source

Prism that focuses on the success value of a Result.

When to use

Use when you have a Result<A, E> and want to read/update A only when it is a Success.

Details

  • getResult fails when the result is a Failure.
  • set(a) produces Result.succeed(a).

See

  • failure — focuses on the failure side
  • Prism — the type this function returns

Signature

declare function success<A, E>(): Prism<Result<A, E>, A>

Example

(Accessing success)

import { Optic, Result } from "effect"
const _ok = Optic.id<Result.Result<number, string>>().compose(Optic.success())
_ok.getResult(Result.succeed(42)) // => Result.succeed(42)
Result.isFailure(_ok.getResult(Result.fail("err"))) // => true

Getters

get

Added in v4.0.0 Source

Reads the focused value from a Lens.

When to use

Use when the optic always focuses exactly one value.

Details

Supports both data-first and data-last forms.

See

  • getResult for optics whose focus may be absent

Signature

declare const get: {
<S, A>(optic: Lens<S, A>): (self: NoInfer<S>) => A;
<S, A>(self: NoInfer<S>, optic: Lens<S, A>): A;
}

getAll

Added in v4.0.0 Source

Extracts all values focused by a Traversal as a plain mutable array.

When to use

Use when you need the focused values as a simple Array<A> for further processing.

Details

  • Returns an empty array when the traversal cannot focus.
  • Always returns a fresh array (safe to mutate).
  • Supports both data-first and data-last forms.

See

  • Traversal — the optic type this operates on

Signature

declare const getAll: {
<S, A>(traversal: Traversal<S, A>): (self: NoInfer<S>) => Array<A>;
<S, A>(self: NoInfer<S>, traversal: Traversal<S, A>): Array<A>;
}

Example

(Collecting positive numbers)

import { Optic, Schema } from "effect"
type S = { readonly values: ReadonlyArray<number> }
const _pos = Optic.id<S>()
.key("values")
.forEach((n) => n.check(Schema.isGreaterThan(0)))
const getPositive = Optic.getAll(_pos)
getPositive({ values: [3, -1, 5] }) // => [3, 5]
getPositive({ values: [-1, -2] }) // => []

getResult

Added in v4.0.0 Source

Attempts to read the focused value from an Optional.

When to use

Use when the optic may not focus and you need the failure as a Result.

Details

Supports both data-first and data-last forms.

See

  • get for optics that always focus

Signature

declare const getResult: {
<S, A>(optic: Optional<S, A>): (self: NoInfer<S>) => Result<A, Issue>;
<S, A>(self: NoInfer<S>, optic: Optional<S, A>): Result<A, Issue>;
}

Models

Iso interface

Added in v4.0.0 Source

A lossless, reversible conversion between types S and A.

When to use

Use when you have a pair of functions that convert back and forth without losing information (e.g. Record ↔ entries, Celsius ↔ Fahrenheit).

  • You want the strongest optic that can be composed with any other.

Details

  • get(s) always succeeds and returns an A.
  • set(a) always succeeds and returns an S.
  • get(set(a)) === a and set(get(s)) equals s (round-trip laws).
  • Extends both Lens and Prism.

See

  • makeIso — constructor
  • Lens — when you only need a one-directional focus into a whole
  • Prism — when the focus may not be present

Signature

interface Iso<in out S, in out A> extends Lens<S, A>, Prism<S, A> {}

Example

(Converting between Celsius and Fahrenheit)

import { Optic } from "effect"
const fahrenheit = Optic.makeIso<number, number>(
(c) => c * 9 / 5 + 32,
(f) => (f - 32) * 5 / 9
)
fahrenheit.get(100) // => 212
fahrenheit.set(32) // => 0

Lens interface

Added in v4.0.0 Source

Focuses on exactly one part A inside a whole S.

When to use

Use when you always have a value to read and need the original S to produce the updated whole, unlike Iso.

Details

  • get(s) always succeeds and returns A.
  • replace(a, s) returns a new S with the focused part replaced.
  • Extends Optional.
  • Composing a Lens with a Prism or Optional produces an Optional.

See

  • makeLens — constructor
  • Iso — when conversion is lossless in both directions
  • Optional — when reading can also fail

Signature

interface Lens<in out S, in out A> extends Optional<S, A> {
readonly get: (s: S) => A;
}

Example

(Focusing on a struct field)

import { Optic } from "effect"
type Person = { readonly name: string; readonly age: number }
const _name = Optic.id<Person>().key("name")
_name.get({ name: "Alice", age: 30 }) // => "Alice"

Optional interface

Added in v4.0.0 Source

The most general optic — both reading and writing can fail.

When to use

Use when the focus may not exist in S and writing a new A back may also fail, for example when the source no longer matches the expected shape. This is the base type extended by Iso, Lens, Prism, and Traversal.

Details

  • getResult(s) returns Result.Success<A> or Result.Failure<SchemaIssue.Issue>.
  • replaceResult(a, s) returns Result.Success<S> or Result.Failure<SchemaIssue.Issue>.
  • replace(a, s) returns the original s on failure (never throws).
  • modify(f) returns the original s on failure (never throws).
  • All operations are pure; inputs are never mutated.

See

  • makeOptional — constructor
  • Lens — when reading always succeeds
  • Prism — when writing always succeeds

Signature

interface Optional<in out S, in out A> {
readonly getResult: (s: S) => Result<A, Issue>;
readonly replace: (a: A, s: S) => S;
readonly replaceResult: (a: A, s: S) => Result<S, Issue>;
at<S, A extends object, Key extends string | number | symbol>(this: Optional<S, A>, key: Key, ..._err: ForbidUnion<A, "cannot use `at` on a union type">): Optional<S, A[Key]>;
check<S, A>(this: Prism<S, A>, ...checks: readonly [Check<A>, Check<A>]): Prism<S, A>;
check<S, A>(this: Optional<S, A>, ...checks: readonly [Check<A>, Check<A>]): Optional<S, A>;
compose<B>(this: Iso<S, A>, that: Iso<A, B>): Iso<S, B>;
compose<B>(this: Lens<S, A>, that: Lens<A, B>): Lens<S, B>;
compose<B>(this: Prism<S, A>, that: Prism<A, B>): Prism<S, B>;
compose<B>(this: Optional<S, A>, that: Optional<A, B>): Optional<S, B>;
forEach<S, A, B>(this: Traversal<S, A>, f: (iso: Iso<A, A>) => Optional<A, B>): Traversal<S, B>;
key<S, A extends object, Key extends string | number | symbol>(this: Lens<S, A>, key: Key, ..._err: ForbidUnion<A, "cannot use `key` on a union type">): Lens<S, A[Key]>;
key<S, A extends object, Key extends string | number | symbol>(this: Optional<S, A>, key: Key, ..._err: ForbidUnion<A, "cannot use `key` on a union type">): Optional<S, A[Key]>;
modify(f: (a: A) => A): (s: S) => S;
modifyAll<S, A>(this: Traversal<S, A>, f: (a: A) => A): (s: S) => S;
notUndefined<S, A>(this: Prism<S, A>): Prism<S, Exclude<A, undefined>>;
notUndefined<S, A>(this: Optional<S, A>): Optional<S, Exclude<A, undefined>>;
omit<S, A, Keys extends readonly Array<keyof A>>(this: Lens<S, A>, keys: Keys, ..._err: ForbidUnion<A, "cannot use `omit` on a union type">): Lens<S, Omit<A, Keys[number]>>;
omit<S, A, Keys extends readonly Array<keyof A>>(this: Optional<S, A>, keys: Keys, ..._err: ForbidUnion<A, "cannot use `omit` on a union type">): Optional<S, Omit<A, Keys[number]>>;
optionalKey<S, A extends object, Key extends string | number | symbol>(this: Lens<S, A>, key: Key, ..._err: ForbidUnion<A, "cannot use `optionalKey` on a union type">): Lens<S, A[Key] | undefined>;
optionalKey<S, A extends object, Key extends string | number | symbol>(this: Optional<S, A>, key: Key, ..._err: ForbidUnion<A, "cannot use `optionalKey` on a union type">): Optional<S, A[Key] | undefined>;
pick<S, A, Keys extends readonly Array<keyof A>>(this: Lens<S, A>, keys: Keys, ..._err: ForbidUnion<A, "cannot use `pick` on a union type">): Lens<S, Pick<A, Keys[number]>>;
pick<S, A, Keys extends readonly Array<keyof A>>(this: Optional<S, A>, keys: Keys, ..._err: ForbidUnion<A, "cannot use `pick` on a union type">): Optional<S, Pick<A, Keys[number]>>;
refine<S, A, B>(this: Prism<S, A>, refinement: (a: A) => a is B, annotations?: Filter): Prism<S, B>;
refine<S, A, B>(this: Optional<S, A>, refinement: (a: A) => a is B, annotations?: Filter): Optional<S, B>;
tag<S, A extends {
readonly _tag: LiteralValue;
}, Tag extends LiteralValue>(this: Prism<S, A>, tag: Tag): Prism<S, Extract<A, {
readonly _tag: Tag;
}>>;
tag<S, A extends {
readonly _tag: LiteralValue;
}, Tag extends LiteralValue>(this: Optional<S, A>, tag: Tag): Optional<S, Extract<A, {
readonly _tag: Tag;
}>>;
}

Example

(Focusing on an optional record key)

import { Optic, Result } from "effect"
type Env = { [key: string]: string }
const _home = Optic.id<Env>().at("HOME")
_home.getResult({ HOME: "/root" }) // => Result.succeed("/root")
Result.isFailure(_home.getResult({ PATH: "/bin" })) // => true
// replace returns original on failure
_home.replace("/new", { PATH: "/bin" }) // => { PATH: "/bin" }

Prism interface

Added in v4.0.0 Source

Focuses on a part A of S that may not be present (e.g. a union variant or a validated subset).

When to use

Use when the focus is conditional — reading can fail (wrong variant, failed validation).

  • Building a new S from A does not require the original S.

Details

  • getResult(s) returns Result.Success<A> when the focus matches, or Result.Failure<SchemaIssue.Issue> with a structured issue.
  • set(a) always succeeds and returns a new S.
  • Extends Optional.
  • Composing two Prisms produces a Prism; composing a Prism with a Lens produces an Optional.

See

Signature

interface Prism<in out S, in out A> extends Optional<S, A> {
readonly set: (a: A) => S;
}

Example

(Narrowing a tagged union)

import { Optic, Result } from "effect"
type Shape =
| { readonly _tag: "Circle"; readonly radius: number }
| { readonly _tag: "Rect"; readonly width: number }
const _circle = Optic.id<Shape>().tag("Circle")
_circle.getResult({ _tag: "Circle", radius: 5 }) // => Result.succeed({ _tag: "Circle", radius: 5 })
Result.isFailure(_circle.getResult({ _tag: "Rect", width: 10 })) // => true

Traversal interface

Added in v4.0.0 Source

An optic that focuses on zero or more elements of type A inside S.

When to use

Use when you want to read/update multiple elements at once (e.g. all items in an array, or a filtered subset).

Details

  • Technically Optional<S, ReadonlyArray<A>> — the focused value is an array of all matched elements.
  • Use .forEach() to add per-element sub-optics (filtering, drilling deeper).
  • Use .modifyAll(f) to map a function over every focused element.
  • Use getAll to extract all focused elements as a plain array.

See

Signature

interface Traversal<in out S, in out A> extends Optional<S, ReadonlyArray<A>> {}

Example

(Traversing array elements with a filter)

import { Optic, Schema } from "effect"
type S = { readonly items: ReadonlyArray<number> }
const _positive = Optic.id<S>()
.key("items")
.forEach((n) => n.check(Schema.isGreaterThan(0)))
const getPositive = Optic.getAll(_positive)
getPositive({ items: [1, -2, 3] }) // => [1, 3]

Transforming

modify

Added in v4.0.0 Source

Transforms the focused value in a source.

When to use

Use when you want to update a focus with a function.

Details

Supports both data-first and data-last forms. A failed focus leaves the source unchanged.

See

  • modifyAll for transforming every value in a traversal

Signature

declare const modify: {
<S, A>(optic: Optional<S, A>, f: (value: NoInfer<A>) => NoInfer<A>): (self: NoInfer<S>) => S;
<S, A>(self: NoInfer<S>, optic: Optional<S, A>, f: (value: NoInfer<A>) => NoInfer<A>): S;
}

modifyAll

Added in v4.0.0 Source

Transforms every value focused by a Traversal.

When to use

Use when you want to update each value selected by a traversal.

Details

Supports both data-first and data-last forms. A failed traversal leaves the source unchanged.

See

  • modify for transforming the focus as a whole
  • getAll for reading every focused value

Signature

declare const modifyAll: {
<S, A>(traversal: Traversal<S, A>, f: (value: NoInfer<A>) => NoInfer<A>): (self: NoInfer<S>) => S;
<S, A>(self: NoInfer<S>, traversal: Traversal<S, A>, f: (value: NoInfer<A>) => NoInfer<A>): S;
}

replace

Added in v4.0.0 Source

Replaces the focused value in a source.

When to use

Use when a failed focus should leave the source unchanged.

Details

Supports both data-first and data-last forms.

See

Signature

declare const replace: {
<S, A>(optic: Optional<S, A>, value: NoInfer<A>): (self: NoInfer<S>) => S;
<S, A>(self: NoInfer<S>, optic: Optional<S, A>, value: NoInfer<A>): S;
}

Attempts to replace the focused value in a source.

When to use

Use when you need an explicit Result for a replacement failure.

Details

Supports both data-first and data-last forms.

See

  • replace for returning the original source on failure

Signature

declare const replaceResult: {
<S, A>(optic: Optional<S, A>, value: NoInfer<A>): (self: NoInfer<S>) => Result<S, Issue>;
<S, A>(self: NoInfer<S>, optic: Optional<S, A>, value: NoInfer<A>): Result<S, Issue>;
}

set

Added in v4.0.0 Source

Builds a source value from a focused value using a Prism.

When to use

Use when the optic can construct the source without an existing source value.

Details

Supports both data-first and data-last forms. The focused value is self.

See

  • replace for updates that use an existing source

Signature

declare const set: {
<S, A>(optic: Prism<S, A>): (self: NoInfer<A>) => S;
<S, A>(self: NoInfer<A>, optic: Prism<S, A>): S;
}