Building Layers
A layer is the recipe that builds a service. Layer<Out, Error, In> reads
like an effect: it produces the services Out, may fail with Error while
constructing them, and requires the services In to do so. Choosing the right
constructor depends on how the implementation comes to exist — a plain value, an
effectful setup, or a resource that must be released.
import { Context, Layer } from "effect"
class Greeter extends Context.Service<Greeter, { readonly greeting: string}>()("myapp/Greeter") {}
// `Layer.succeed` — the implementation is a value you already have.const GreeterLive = Layer.succeed(Greeter, { greeting: "Hello" })Layers are lazy (nothing runs until the layer is provided, built, or launched), scoped (finalizers run when the owning scope closes), and memoized by default (the same layer value, shared across a graph, acquires a single instance of its service).
The three constructors
Section titled “The three constructors”Layer.succeed — a ready-made value
Section titled “Layer.succeed — a ready-made value”Use it when the service is just a plain object, with no setup work:
import { Context, Layer } from "effect"
class Config extends Context.Service<Config, { readonly apiUrl: string}>()("myapp/Config") {}
const ConfigLive = Layer.succeed(Config, { apiUrl: "https://api.example.com" })Layer.effect — effectful construction
Section titled “Layer.effect — effectful construction”Use it when building the service requires running an effect: reading config, allocating state, or depending on another service. The effect runs once, when the layer is built, and its result is memoized:
import { Context, Effect, Layer, Ref } from "effect"
class Counter extends Context.Service<Counter, { readonly increment: Effect.Effect<number>}>()("myapp/Counter") { static readonly layer = Layer.effect( Counter, Effect.gen(function*() { // Setup work runs once at build time — here, allocating shared state. const ref = yield* Ref.make(0)
const increment = Ref.updateAndGet(ref, (n) => n + 1)
return Counter.of({ increment }) }) )}Scoped layers — resources that must be released
Section titled “Scoped layers — resources that must be released”In Effect v4 there’s no separate Layer.scoped: Layer.effect already accepts
an effect that requires a Scope. Acquire the resource with
Effect.acquireRelease and the layer ties its cleanup to the layer’s own
lifetime — when the layer is torn down, the resource is released.
import { Context, Effect, Layer } from "effect"
class Pool extends Context.Service<Pool, { readonly run: <A>(work: () => A) => Effect.Effect<A>}>()("myapp/Pool") { static readonly layer = Layer.effect( Pool, Effect.gen(function*() { // acquireRelease pairs setup with a guaranteed release. The `Scope` it // introduces is absorbed by `Layer.effect`, so it never leaks into the // layer's requirements. const connections = yield* Effect.acquireRelease( Effect.sync(() => { // open the pool return { close: () => {} } }), (pool) => Effect.sync(() => pool.close()) )
const run = <A>(work: () => A) => Effect.sync(() => work()) return Pool.of({ run }) }) )}Providing a layer
Section titled “Providing a layer”A layer is inert until you attach it to a program. Effect.provide discharges
the matching requirements and returns an effect that no longer needs them:
import { Effect } from "effect"import { Counter } from "./Counter.ts"
const program = Effect.gen(function*() { const counter = yield* Counter yield* counter.increment return yield* counter.increment // 2})
// After providing, the `Counter` requirement is gone and the effect can run.const runnable: Effect.Effect<number> = program.pipe( Effect.provide(Counter.layer))
Effect.runFork(runnable)Effect.provide attaches a layer to an effect. Layer.provide instead feeds
one layer’s output into another layer’s requirements, producing a new layer —
that’s how you wire dependencies between services and is covered in
Layer composition.
For programs whose whole point is the layer’s lifecycle — a server that should
stay up until interrupted — Layer.launch builds the layer and keeps it alive:
import { Effect, Layer } from "effect"import { Pool } from "./Pool.ts"
// Builds Pool.layer, holds it open forever, and releases on interruption.Effect.runFork(Layer.launch(Pool.layer))Which constructor?
Section titled “Which constructor?”| Constructor | When |
|---|---|
Layer.succeed | The service is a plain value, no setup needed |
Layer.sync | Same, but build it lazily at layer-build time |
Layer.effect | Construction is effectful or depends on other services |
Layer.effect + Effect.acquireRelease | The service owns a resource to release |
Layer.effectDiscard | Run setup work / background tasks, provide nothing |
Layer.suspend | Defer construction; choose or break a cycle at build time |
Layer.unwrap | Pick the concrete layer from an Effect (e.g. from config) |
Layer.mergeAll | Combine several independent layers into one |
Memoization and sharing
Section titled “Memoization and sharing”By default a layer value is memoized: provide the same layer in two places and the service is constructed once and shared. Sharing is keyed on layer identity, so constructing the same recipe twice yields two distinct values that each acquire their own instance.
Layer.fresh
Section titled “Layer.fresh”Forces a layer to be rebuilt even if the same value is provided more than once — use it when two parts of the app must get separate instances (e.g. two independent client sessions).
import { Context, Effect, Layer, Ref } from "effect"
class Counter extends Context.Service<Counter, { readonly id: number }>()("Counter") {}
const program = Effect.gen(function*() { const next = yield* Ref.make(0) const counterLayer = Layer.effect( Counter, Effect.gen(function*() { const id = yield* Ref.updateAndGet(next, (n) => n + 1) return { id } }) )
// `counterLayer` shared => one instance; `Layer.fresh(counterLayer)` => a new one. return Layer.fresh(counterLayer)})Layer constructors reference
Section titled “Layer constructors reference”Every constructor below produces a Layer. Constructors come in a single-service
form (you pass a Context.Key/service class and a value/effect) and a whole-context
form (the *Context variants) that can provide several services at once.
Layer.succeed
Section titled “Layer.succeed”Provides one already-constructed service value. Also supports a curried “key
first” form for use in a pipe.
import { Context, Layer } from "effect"
class Config extends Context.Service<Config, { readonly port: number }>()("Config") {}
const ConfigLive = Layer.succeed(Config, { port: 8080 })// => Layer<Config>
// Curried (data-last) form — handy when piping a value in:const fromValue = ({ port: 8080 } as const).pipe(Layer.succeed(Config))// => Layer<Config>Layer.succeedContext
Section titled “Layer.succeedContext”Provides every service contained in an already-built Context. Use it when you
have a multi-service Context value rather than a single implementation.
import { Context, Effect, Layer } from "effect"
class Config extends Context.Service<Config, { readonly port: number }>()("Config") {}class Logger extends Context.Service<Logger, { readonly log: (msg: string) => Effect.Effect<void>}>()("Logger") {}
const context = Context.make(Config, { port: 8080 }).pipe( Context.add(Logger, { log: (msg) => Effect.sync(() => console.log(msg)) }))
const Live = Layer.succeedContext(context)// => Layer<Config | Logger>Layer.sync
Section titled “Layer.sync”Lazily builds one service value with a thunk that runs at layer-build time. Use
it instead of succeed when the value should not be computed until the layer is
actually built (e.g. it reads Date.now() or allocates a mutable cell).
import { Context, Layer } from "effect"
class Clock extends Context.Service<Clock, { readonly startedAt: number }>()("Clock") {}
const ClockLive = Layer.sync(Clock, () => ({ startedAt: Date.now() }))// => Layer<Clock> (the thunk runs when the layer is built, not now)Layer.syncContext
Section titled “Layer.syncContext”The lazy, whole-Context version of succeedContext: the factory runs at
build time and returns a Context of one or more services.
import { Context, Layer } from "effect"
class Config extends Context.Service<Config, { readonly env: string }>()("Config") {}
const Live = Layer.syncContext(() => Context.make(Config, { env: process.env.NODE_ENV ?? "development" }))// => Layer<Config>Layer.effect
Section titled “Layer.effect”Builds one service from an Effect, run once when the layer is built. The
effect can depend on other services and acquire scoped resources; any Scope
requirement is absorbed (it does not appear in the layer’s In type), and a
typed failure becomes the layer’s Error.
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, { readonly query: (sql: string) => Effect.Effect<string>}>()("Database") {}
const DatabaseLive = Layer.effect( Database, Effect.gen(function*() { yield* Effect.log("connecting…") return Database.of({ query: (sql) => Effect.succeed(`rows for: ${sql}`) }) }))// => Layer<Database, never, never>Layer.effectContext
Section titled “Layer.effectContext”The whole-Context version of effect: the effect produces a Context of one
or more services. Use it when a single effectful setup builds several services
together.
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, { readonly query: (sql: string) => Effect.Effect<string>}>()("Database") {}class Cache extends Context.Service<Cache, { readonly get: (k: string) => Effect.Effect<string>}>()("Cache") {}
const Live = Layer.effectContext( Effect.gen(function*() { const db = Database.of({ query: (sql) => Effect.succeed(`rows: ${sql}`) }) const cache = Cache.of({ get: (k) => Effect.succeed(`cached: ${k}`) }) return Context.make(Database, db).pipe(Context.add(Cache, cache)) }))// => Layer<Database | Cache>Layer.effectDiscard
Section titled “Layer.effectDiscard”Runs an effect for its side effects during layer construction and provides no
services (Out is never). Ideal for startup logging, warm-up work, or spawning
a background fiber whose lifetime is tied to the layer scope.
import { Effect, Layer } from "effect"
const BackgroundTask = Layer.effectDiscard( Effect.gen(function*() { yield* Effect.log("starting background task…") yield* Effect.gen(function*() { while (true) { yield* Effect.sleep("5 seconds") yield* Effect.log("tick") } }).pipe(Effect.forkScoped) // interrupted when the layer scope closes }))// => Layer<never> — provides nothing, just runs workLayer.empty
Section titled “Layer.empty”A no-op layer: provides nothing, never fails, has no requirements, and does no construction or finalization. Useful as the inert branch when conditionally composing layers.
import { Console, Layer } from "effect"
declare const verbose: boolean
const StartupLog = verbose ? Layer.effectDiscard(Console.log("starting")) : Layer.empty// => Layer<never>Layer.suspend
Section titled “Layer.suspend”Defers the construction of a layer until it is first built; the factory’s result is then memoized with normal sharing semantics. New in v4 — use it to choose a layer based on build-time state, or to break an initialization cycle by deferring a reference to a layer defined later.
import { Context, Layer } from "effect"
class Config extends Context.Service<Config, string>()("Config") {}
declare const useProd: boolean
const ConfigLive = Layer.suspend(() => useProd ? Layer.succeed(Config, "https://api.example.com") : Layer.succeed(Config, "http://localhost:3000"))// => Layer<Config> — the branch is chosen when the layer is builtLayer.unwrap
Section titled “Layer.unwrap”Flattens an Effect that produces a Layer into a single layer, combining both
error and requirement channels. This is the canonical way to choose a concrete
implementation from configuration: read config in the effect, then return the
layer to use.
import { Config, Context, Effect, Layer } from "effect"
class MessageStore extends Context.Service<MessageStore, { readonly append: (msg: string) => Effect.Effect<void>}>()("MessageStore") { static readonly layerInMemory = Layer.sync(MessageStore, () => { const messages: Array<string> = [] return MessageStore.of({ append: (msg) => Effect.sync(() => { messages.push(msg) }) }) })
static readonly layerNoop = Layer.succeed(MessageStore, { append: () => Effect.void })
// Choose the concrete layer at build time, based on an env var. static readonly layer = Layer.unwrap( Effect.gen(function*() { const inMemory = yield* Config.boolean("IN_MEMORY").pipe(Config.withDefault(true)) return inMemory ? MessageStore.layerInMemory : MessageStore.layerNoop }) )}// => Layer<MessageStore, ConfigError>Building a layer from configuration is common enough that it has its own page — see Layers from config.
Layer.fromBuild / Layer.fromBuildMemo
Section titled “Layer.fromBuild / Layer.fromBuildMemo”Low-level escape hatches that construct a layer from a function receiving a
MemoMap and a Scope. fromBuildMemo adds automatic memoization keyed on the
returned layer value. Most code never needs these — they back the higher-level
constructors above.
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, { readonly query: (sql: string) => Effect.Effect<string>}>()("Database") {}
const DatabaseLive = Layer.fromBuild(() => Effect.sync(() => Context.make(Database, { query: (sql) => Effect.succeed("result") })))// => Layer<Database>Building a layer manually
Section titled “Building a layer manually”Usually you hand a layer to Effect.provide and let the runtime build it. When
you need the produced Context directly — testing, wiring custom runtimes —
build it yourself.
Layer.build
Section titled “Layer.build”Builds the layer into its Context, using the ambient Scope and memo map. The
returned effect requires a Scope, so resources live until that scope closes.
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, { readonly query: (sql: string) => Effect.Effect<string>}>()("Database") {}
const program = Effect.gen(function*() { const context = yield* Layer.build(Database.layer) const db = Context.get(context, Database) return yield* db.query("SELECT 1")}).pipe(Effect.scoped) // closing this scope releases the layerLayer.buildWithScope
Section titled “Layer.buildWithScope”Like build, but you pass the Scope explicitly, giving you direct control over
when the layer’s resources are released.
import { Context, Effect, Layer } from "effect"
declare const SomeLayer: Layer.Layer<unknown>
const program = Effect.gen(function*() { const scope = yield* Effect.scope const context = yield* Layer.buildWithScope(SomeLayer, scope) return context})Layer.launch
Section titled “Layer.launch”Builds the layer and blocks forever, keeping it alive until the fiber is interrupted — at which point the layer scope closes and finalizers run. This is how “the whole app is a layer” programs (servers, daemons) are run.
import { Effect, Layer } from "effect"import { Pool } from "./Pool.ts"
Effect.runFork(Layer.launch(Pool.layer))// runs until interrupted, then releases PoolObserving and transforming construction
Section titled “Observing and transforming construction”These combinators take a layer and return a layer, letting you react to success or failure during construction without changing the services provided.
Layer.tap / Layer.tapError / Layer.tapCause
Section titled “Layer.tap / Layer.tapError / Layer.tapCause”Run an effect when the layer succeeds (tap, receiving the produced Context),
fails with a typed error (tapError), or fails with any Cause (tapCause).
The layer’s output and error are preserved.
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, { readonly url: string }>()("Database") {}
const DatabaseLive = Layer.succeed(Database, { url: "postgres://…" }).pipe( Layer.tap((ctx) => Effect.log(`connected to ${Context.get(ctx, Database).url}`)), Layer.tapError((e) => Effect.log(`build failed: ${e}`)))Layer.catch / Layer.catchTag / Layer.catchCause
Section titled “Layer.catch / Layer.catchTag / Layer.catchCause”Recover from a construction failure by switching to another layer. catch
handles every typed error, catchTag matches specific tagged errors, and
catchCause receives the full Cause (defects, interruption included).
import { Context, Effect, Layer } from "effect"
class Config extends Context.Service<Config, { readonly apiUrl: string }>()("Config") {}
const primary = Layer.effect(Config, Effect.fail("boom" as const))const recovered = primary.pipe( Layer.catch(() => Layer.succeed(Config, { apiUrl: "http://localhost" })))// => Layer<Config> — falls back instead of failingLayer.orDie
Section titled “Layer.orDie”Converts every typed construction failure into an unrecoverable defect, removing the error from the layer’s type. Use only when a failure here means the program cannot meaningfully continue.
import { Context, Effect, Layer } from "effect"
class Config extends Context.Service<Config, { readonly apiUrl: string }>()("Config") {}
const layer = Layer.effect(Config, Effect.fail("missing config" as const)).pipe( Layer.orDie)// => Layer<Config, never> — failure becomes a defectLayer.updateService
Section titled “Layer.updateService”Replaces a service in the layer’s required context with a transformed version — useful for adapting or wrapping an existing service implementation during construction.
import { Context, Effect, Layer } from "effect"
class Logger extends Context.Service<Logger, { readonly log: (msg: string) => Effect.Effect<void>}>()("Logger") {}
declare const AppLayer: Layer.Layer<unknown, never, Logger>
const withPrefixedLogger = AppLayer.pipe( Layer.updateService(Logger, (logger) => ({ log: (msg) => logger.log(`[app] ${msg}`) })))Testing helpers
Section titled “Testing helpers”Layer.mock
Section titled “Layer.mock”Builds a layer from a partial service implementation: any omitted member that
is an Effect, Stream, Channel, or function returning one fails with an
UnimplementedError if exercised, while non-effect properties remain required.
import { Context, Effect, Layer } from "effect"
class UserService extends Context.Service<UserService, { readonly config: { apiUrl: string } readonly getUser: (id: string) => Effect.Effect<{ id: string; name: string }> readonly deleteUser: (id: string) => Effect.Effect<void>}>()("UserService") {}
const testLayer = Layer.mock(UserService, { config: { apiUrl: "https://test" }, // required, non-effect property getUser: (id) => Effect.succeed({ id, name: "Test" }) // deleteUser omitted — throws UnimplementedError if called})// => Layer<UserService>Tracing during construction
Section titled “Tracing during construction”Layer.span, Layer.withSpan, Layer.parentSpan, and Layer.withParentSpan
wrap layer construction in a trace span so that all setup work is attributed to a
named span that ends when the layer scope closes.
import { Context, Effect, Layer } from "effect"
class Database extends Context.Service<Database, { readonly query: (sql: string) => Effect.Effect<string>}>()("Database") {}
const DatabaseLive = Layer.effect( Database, Effect.gen(function*() { yield* Effect.log("connecting") // traced under "db-init" return Database.of({ query: (sql) => Effect.succeed(`rows: ${sql}`) }) })).pipe(Layer.withSpan("db-init", { attributes: { dbType: "postgres" } }))Type utilities
Section titled “Type utilities”A handful of type-level helpers extract a layer’s components or constrain it:
Layer.Success<L>— the services a layer provides (Out).Layer.Error<L>— the construction error type (Error).Layer.Services<L>— the dependency requirements (In).Layer.satisfiesSuccessType<T>()/satisfiesErrorType<T>()/satisfiesServicesType<T>()— compile-time assertions that a layer’sOut,Error, orInextendsT.
import { Layer } from "effect"
declare const AppLayer: Layer.Layer<{ db: unknown }, Error, { config: unknown }>
type Provided = Layer.Success<typeof AppLayer> // { db: unknown }type Fails = Layer.Error<typeof AppLayer> // Errortype Needs = Layer.Services<typeof AppLayer> // { config: unknown }Next, see how layers depend on one another and how to wire dependency graphs in Layer composition.