# 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.

```ts
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 })
})
```

The signature is `Context.Reference<Shape>(id, { defaultValue: () => Shape })`.
The `id` is a globally unique string key; `defaultValue` is a thunk that is run
at most once and then cached on the reference. The resulting value is typed
`Context.Reference<Shape>`, which extends `Context.Service<never, Shape>` — note
the `never`, which is exactly why a reference never contributes a requirement.

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:

```ts
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)
```

## Overriding a reference

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.

```ts
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"
```

There are three interchangeable ways to set a reference, depending on where the
value comes from:

```ts
import { Effect, Layer } from "effect"
import { FeatureFlags } from "./FeatureFlags.ts"

const value = { betaCheckout: true, darkMode: true }

// 1. Scoped to a single effect (and the fibers it starts).
const scoped = Effect.provideService(program, FeatureFlags, value)

// 2. As a layer, when the value is assembled alongside the rest of your wiring.
const layer = Layer.succeed(FeatureFlags, value)

// 3. Read it back anywhere with `yield*` — the requirement stays `never`.
const read = Effect.gen(function*() {
  const flags = yield* FeatureFlags
  return flags.darkMode
})
```

Because a reference *is* a service key, `Layer.succeed(FeatureFlags, { ... })`
works when the value should be assembled alongside the rest of your
application's layers, and child fibers inherit the context active at the point
they are started — so an override applies to everything forked inside its scope.

## Reference vs. Service

| | `Context.Service` | `Context.Reference` |
| --- | --- | --- |
| Default value | none | required (`defaultValue`) |
| Missing at runtime | compile error / must provide | uses the default |
| Appears in requirements | yes | no |
| Typical use | database, HTTP client, repositories | flags, config defaults, tunables |

Reach for a reference when *omitting* the dependency should be valid and have a
sensible fallback. Reach for a [Service](https://effect.plants.sh/services-and-layers/services/) when a
missing implementation should stop the program from compiling.

## Defaults backed by configuration

A common pattern is a reference whose default is overridden from
[Configuration](https://effect.plants.sh/configuration/) at startup. The reference gives you a value to
read everywhere, and a layer fills it in from the environment when present:

```ts
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](https://effect.plants.sh/services-and-layers/layer-from-config/)
for choosing whole implementations based on configuration.

## Built-in references

The Effect runtime exposes its own execution settings and diagnostic metadata as
references in the `References` module. Runtime services — logging, tracing,
scheduling, concurrency — read these keys from the current context, so you can
read the current value with `yield*` and override it for a scope with
`Effect.provideService`, `Layer.succeed`, or (for some) a dedicated `Effect.*`
helper.

```ts
import { Effect, References } from "effect"

const program = Effect.gen(function*() {
  // Read the current concurrency limit (default: "unbounded").
  const current = yield* References.CurrentConcurrency
  console.log(current) // => "unbounded"
})

// Override it for the scope of `program` and the fibers it starts.
const limited = Effect.provideService(program, References.CurrentConcurrency, 4)
```

Because each built-in reference has a runtime default, every one of these reads
as an effect with `never` requirements — you never have to provide them to
compile. Overrides are context values, so a scoped override ends when the
provided effect completes.

:::tip[Prefer the high-level helper when one exists]
Several references have a purpose-built `Effect.*` combinator (noted below).
Prefer those for everyday use; reach into `References` directly when you need a
knob with no dedicated helper, or want to read the current value.
:::

### CurrentConcurrency

`Context.Reference<number | "unbounded">` — the default concurrency limit read
by combinators like `Effect.forEach({ concurrency: "inherit" })`. Default is
`"unbounded"`. The dedicated helper is `Effect.withConcurrency`.

```ts
import { Effect, References } from "effect"

const read = Effect.gen(function*() {
  return yield* References.CurrentConcurrency
})

// Equivalent ways to cap inherited concurrency at 4:
const a = Effect.provideService(read, References.CurrentConcurrency, 4)
const b = Effect.withConcurrency(read, 4) // => 4
```

See [Concurrency options](https://effect.plants.sh/concurrency/concurrency-options/) for how `"inherit"`
consults this reference.

### MinimumLogLevel

`Context.Reference<LogLevel>` — the severity threshold below which log entries
are filtered out entirely. Default is `"Info"`, so `Debug`/`Trace` entries are
dropped unless you lower it.

```ts
import { Effect, References } from "effect"

const verbose = Effect.gen(function*() {
  yield* Effect.logDebug("detailed trace") // emitted only when min level <= Debug
}).pipe(
  Effect.provideService(References.MinimumLogLevel, "Debug")
)
```

### CurrentLogLevel

`Context.Reference<Severity>` — the severity assigned to `Effect.log` entries
that do not specify a level. Default is `"Info"`. This sets the *level of the
entry*; `MinimumLogLevel` decides whether that entry survives filtering.

```ts
import { Effect, References } from "effect"

const program = Effect.gen(function*() {
  const level = yield* References.CurrentLogLevel
  console.log(level) // => "Warn"
  yield* Effect.log("treated as a Warn-level entry")
}).pipe(
  Effect.provideService(References.CurrentLogLevel, "Warn")
)
```

### CurrentLogAnnotations

`Context.Reference<ReadonlyRecord<string, unknown>>` — key/value metadata
attached to every log entry emitted in the current context. Default is `{}`. The
dedicated helpers are `Effect.annotateLogs` (per effect) and
`Effect.annotateLogsScoped` (for the rest of the current scope).

```ts
import { Effect, References } from "effect"

const annotated = Effect.gen(function*() {
  const current = yield* References.CurrentLogAnnotations
  console.log(current) // => { requestId: "req-123" }
  yield* Effect.log("handling request") // includes requestId=req-123
}).pipe(
  Effect.provideService(References.CurrentLogAnnotations, { requestId: "req-123" })
)

// Usually written with the helper instead:
const sameThing = Effect.annotateLogs(
  Effect.log("handling request"),
  { requestId: "req-123" }
)
```

### CurrentLogSpans

`Context.Reference<ReadonlyArray<[label: string, timestamp: number]>>` — the
stack of labeled time spans included with log entries to show how long an
operation has been running. Default is `[]`. Normally managed by `Effect.withLogSpan`.

```ts
import { Effect, References } from "effect"

const program = Effect.gen(function*() {
  const spans = yield* References.CurrentLogSpans
  console.log(spans.map(([label]) => label)) // => ["db-query"]
}).pipe(
  Effect.provideService(References.CurrentLogSpans, [["db-query", 0]])
)
```

### UnhandledLogLevel

`Context.Reference<Severity | undefined>` — the severity at which a pool
finalizer reports an unhandled error. Default is `"Error"`. Providing
`undefined` suppresses the report entirely; it does **not** fall back to
`CurrentLogLevel`.

```ts
import { Effect, References } from "effect"

// Silence unhandled pool-finalizer error reports for this scope.
const quiet = Effect.provideService(
  myProgram,
  References.UnhandledLogLevel,
  undefined
)

declare const myProgram: Effect.Effect<void>
```

### CurrentLoggers

`Context.Reference<ReadonlySet<Logger<unknown, any>>>` — the complete set of
`Logger` instances that receive log entries. Default contains the built-in
default logger and the tracer logger. Provide this to replace which loggers fire.

```ts
import { Effect, Logger, References } from "effect"

const program = Effect.gen(function*() {
  const loggers = yield* References.CurrentLoggers
  console.log(loggers.size) // => 1
  yield* Effect.log("only the JSON logger sees this")
}).pipe(
  Effect.provideService(References.CurrentLoggers, new Set([Logger.formatJson]))
)
```

### LogToStderr

`Context.Reference<boolean>` — when `true`, the built-in console loggers call
`console.error` instead of `console.log`. Default is `false`. Useful to keep
stdout reserved for protocol or data output (e.g. a CLI emitting JSON).

```ts
import { Effect, References } from "effect"

const cli = Effect.gen(function*() {
  yield* Effect.log("diagnostic noise goes to stderr")
  console.log(JSON.stringify({ result: "ok" })) // stdout stays clean
}).pipe(
  Effect.provideService(References.LogToStderr, true)
)
```

### TracerEnabled

`Context.Reference<boolean>` — controls whether spans are registered with the
tracer. Default is `true`; set `false` to skip span registration and minimize
tracing overhead. The dedicated helper is `Effect.withTracerEnabled`.

```ts
import { Effect, References } from "effect"

const untraced = Effect.gen(function*() {
  const enabled = yield* References.TracerEnabled
  console.log(enabled) // => false
}).pipe(
  Effect.provideService(References.TracerEnabled, false)
)

// Equivalent helper:
const sameThing = Effect.withTracerEnabled(untraced, false)
```

### TracerTimingEnabled

`Context.Reference<boolean>` — controls whether spans capture timing
information. Default is `true`; when `false`, span times are always zero. The
dedicated helper is `Effect.withTracerTiming`.

```ts
import { Effect, References } from "effect"

const program = Effect.gen(function*() {
  const enabled = yield* References.TracerTimingEnabled
  console.log(enabled) // => false
})

const noTiming = Effect.withTracerTiming(program, false)
```

### TracerSpanAnnotations

`Context.Reference<ReadonlyRecord<string, unknown>>` — metadata attached to
every new span created in the current context. Default is `{}`. The dedicated
helper is `Effect.annotateSpans` (vs. `Effect.annotateCurrentSpan` for just the
active span).

```ts
import { Effect, References } from "effect"

const program = Effect.gen(function*() {
  const annotations = yield* References.TracerSpanAnnotations
  console.log(annotations) // => { service: "checkout" }
}).pipe(
  Effect.provideService(References.TracerSpanAnnotations, { service: "checkout" })
)

// Equivalent helper:
const sameThing = Effect.annotateSpans(program, { service: "checkout" })
```

### TracerSpanLinks

`Context.Reference<ReadonlyArray<Tracer.SpanLink>>` — span links attached to
every new span, connecting spans that are not in a parent-child relationship.
Default is `[]`.

```ts
import { Effect, References, Tracer } from "effect"

const link: Tracer.SpanLink = {
  span: Tracer.externalSpan({ spanId: "span-1", traceId: "trace-1" }),
  attributes: { relationship: "follows-from" }
}

const program = Effect.gen(function*() {
  const links = yield* References.TracerSpanLinks
  console.log(links.length) // => 1
}).pipe(
  Effect.provideService(References.TracerSpanLinks, [link])
)
```

### CurrentStackFrame

`Context.Reference<References.StackFrame | undefined>` — the captured
stack-frame chain for the running fiber, used by Effect and Layer tracing to
attach call-site info to failures and interruptions. Normally managed by tracing
APIs, not provided by application code. A `StackFrame` is
`{ name: string; stack: () => string | undefined; parent: StackFrame | undefined }`.

```ts
import { Effect, References } from "effect"

const program = Effect.gen(function*() {
  const frame = yield* References.CurrentStackFrame
  console.log(frame?.name) // => undefined (no frame captured by default)
})
```

:::note[Scheduler and tracer-filtering references]
`References` also re-exports lower-level scheduler and tracing knobs from the
`Scheduler` and `Tracer` modules: `Scheduler` (the active scheduler),
`MaxOpsBeforeYield` (operation budget before a fiber yields; default `2048`),
`PreventSchedulerYield` (bypass yield checks; default `false`), `Tracer` (the
active tracer service), `CurrentTraceLevel` / `MinimumTraceLevel` (dynamic span
filtering), and `DisablePropagation` (mark spans non-propagating). They behave
exactly like the references above — read with `yield*`, override with
`Effect.provideService`.
:::

See [Logging](https://effect.plants.sh/observability/logging/) and [Tracing](https://effect.plants.sh/observability/tracing/)
for the high-level APIs that read these references, and
[Concurrency options](https://effect.plants.sh/concurrency/concurrency-options/) for `CurrentConcurrency`.