Skip to content

References

Some dependencies should always be available without forcing every caller to provide them — a feature flag, a default page size, a logger that falls back to the console. Context.Reference defines a service with a lazily computed default value. Unlike Context.Service, a reference never shows up as an unsatisfied requirement: if nobody overrides it, the default is used.

import { Context } from "effect"
// A reference holds a value and a default. The default is computed lazily the
// first time the reference is read, then cached.
export const FeatureFlags = Context.Reference<{
readonly betaCheckout: boolean
readonly darkMode: boolean
}>("myapp/FeatureFlags", {
defaultValue: () => ({ betaCheckout: false, darkMode: false })
})

Reading a reference is identical to reading any other service — yield* it — but the effect’s requirement type stays never, because the default guarantees a value is always present:

import { Effect } from "effect"
import { FeatureFlags } from "./FeatureFlags.ts"
// Note the requirement is `never`: this program runs without providing anything.
const program: Effect.Effect<void> = Effect.gen(function*() {
const flags = yield* FeatureFlags
if (flags.betaCheckout) {
yield* Effect.log("Using beta checkout flow")
} else {
yield* Effect.log("Using stable checkout flow")
}
})
// Runs as-is — the default `{ betaCheckout: false, darkMode: false }` is used.
Effect.runFork(program)

You override a reference exactly like a normal service: provide a different value for the scope of an effect. Anything inside that scope sees the override; anything outside falls back to the default.

import { Effect } from "effect"
import { FeatureFlags } from "./FeatureFlags.ts"
const withBeta = program.pipe(
// `provideService` swaps in a new value for the duration of this effect.
Effect.provideService(FeatureFlags, {
betaCheckout: true,
darkMode: true
})
)
Effect.runFork(withBeta) // logs "Using beta checkout flow"

Because a reference is a service key, you can also override it with a layer (Layer.succeed(FeatureFlags, { ... })) when the value should be assembled alongside the rest of your application’s layers.

Context.ServiceContext.Reference
Default valuenonerequired (defaultValue)
Missing at runtimecompile error / must provideuses the default
Appears in requirementsyesno
Typical usedatabase, HTTP client, repositoriesflags, config defaults, tunables

Reach for a reference when omitting the dependency should be valid and have a sensible fallback. Reach for a Service when a missing implementation should stop the program from compiling.

A common pattern is a reference whose default is overridden from Configuration at startup. The reference gives you a value to read everywhere, and a layer fills it in from the environment when present:

import { Config, Context, Effect, Layer } from "effect"
// One shared default keeps the reference and the config fallback in sync.
const DEFAULT_PAGE_SIZE = 20
export const PageSize = Context.Reference<number>("myapp/PageSize", {
defaultValue: () => DEFAULT_PAGE_SIZE
})
// Build a layer that reads PAGE_SIZE from config, falling back to the same
// default the reference uses when the env var is absent.
export const PageSizeFromConfig = Layer.effect(
PageSize,
Config.number("PAGE_SIZE").pipe(Config.withDefault(DEFAULT_PAGE_SIZE))
)

Provide PageSizeFromConfig to read from the environment; omit it and every reader still gets 20. See Layers from config for choosing whole implementations based on configuration.