ConfigProvider.ts
ConfigProvider.ts overview
Section titled “ConfigProvider.ts overview”Data sources used by Config to load raw configuration values. A
ConfigProvider reads paths from places such as environment variables,
JavaScript objects, .env contents, or directories, and returns a uniform
Node shape that config schemas can decode. The module also includes helpers
for composing providers, changing paths, and installing providers through
layers.
Since v4.0.0
Exports Grouped by Category
Section titled “Exports Grouped by Category”ConfigProviders
Section titled “ConfigProviders”fromDir
Section titled “fromDir”Creates a ConfigProvider that reads configuration from a directory tree
on disk, where each file is a leaf value and each directory is a container.
When to use
Use when you expose each config key as a file under a directory, such as Kubernetes ConfigMap or Secret volume mounts.
Details
Resolution tries a regular file first and returns a Value node with
trimmed file contents. If the file read fails, it tries a directory and
returns a Record node with immediate child names as keys. If both fail with
NotFound, it returns undefined. Other platform failures return
SourceError.
Requires Path and FileSystem in the Effect context. Defaults to root
path /; override with { rootPath: "/etc/config" }.
Example (Reading config from a directory)
import { ConfigProvider, Effect } from "effect"
const program = Effect.gen(function* () { const provider = yield* ConfigProvider.fromDir({ rootPath: "/etc/myapp" }) return provider})See
fromEnv– for environment variablesfromDotEnv– for.envfiles
Signature
declare const fromDir: (options?: { readonly rootPath?: string | undefined}) => Effect.Effect<ConfigProvider, never, Path_.Path | FileSystem.FileSystem>Since v4.0.0
fromDotEnvContents
Section titled “fromDotEnvContents”Creates a ConfigProvider by parsing the string contents of a .env file.
When to use
Use when you already have the .env contents as a string, such as contents
fetched from a remote store or embedded in a test.
Details
Supports export prefixes, single/double/backtick quoting, inline comments,
and escaped newlines. Variable expansion (for example, ${VAR}) is disabled
by default; enable with { expandVariables: true }.
Parsing is based on the dotenv / dotenv-expand algorithm.
Internally delegates to fromEnv with the parsed key-value pairs.
Example (Parsing .env contents)
import { ConfigProvider } from "effect"
const contents = `HOST=localhostPORT=3000# this is a comment`
const provider = ConfigProvider.fromDotEnvContents(contents)See
fromDotEnv– loads a.envfile from diskfromEnv– for raw environment variable access
Signature
declare const fromDotEnvContents: ( lines: string, options?: { readonly expandVariables?: boolean | undefined }) => ConfigProviderSince v4.0.0
fromEnv
Section titled “fromEnv”Creates a ConfigProvider backed by environment variables.
When to use
Use to read configuration from process.env, which is the default when no
provider is explicitly set, or pass a custom env record for testing or
non-Node runtimes.
Details
Path segments are joined with _ for direct lookup, and env var names are
also split on _ to build a trie for child key discovery. This means
DATABASE_HOST=localhost is accessible at both path ["DATABASE_HOST"]
and ["DATABASE", "HOST"]. If all immediate children of a trie node have
purely numeric names, the node is reported as an Array; otherwise as a
Record.
The default environment merges process.env and import.meta.env (when
available). Override by passing { env: { ... } }.
Never fails with SourceError — all lookups are synchronous.
Example (Reading from a custom env record)
import { Config, ConfigProvider, Effect } from "effect"
const provider = ConfigProvider.fromEnv({ env: { DATABASE_HOST: "localhost", DATABASE_PORT: "5432" }})
const host = Config.string("HOST").parse(provider.pipe(ConfigProvider.nested("DATABASE")))
// Effect.runSync(host) // "localhost"See
fromUnknown– for JSON objectsconstantCase– bridge camelCase keys to SCREAMING_SNAKE_CASE
Signature
declare const fromEnv: (options?: { readonly env?: Record<string, string> | undefined }) => ConfigProviderSince v2.0.0
fromUnknown
Section titled “fromUnknown”Creates a ConfigProvider backed by an in-memory JavaScript value
(typically a parsed JSON object).
When to use
Use when you need deterministic config from an in-memory JavaScript value, such as in tests, embedded config, or parsed JSON.
Details
Path traversal follows standard JS rules: string segments index into object
keys, numeric segments index into arrays. Returns undefined for any path
that cannot be resolved. Never fails with SourceError.
Primitive values (number, boolean, bigint) are stringified via
String(...).
Example (Providing config from a plain object)
import { Config, ConfigProvider, Effect } from "effect"
const provider = ConfigProvider.fromUnknown({ database: { host: "localhost", port: 5432 }})
const host = Config.string("host").parse(provider.pipe(ConfigProvider.nested("database")))
// Effect.runSync(host) // "localhost"See
fromEnv– for environment variablesmake– for custom backing stores
Signature
declare const fromUnknown: (root: unknown) => ConfigProviderSince v4.0.0
combinators
Section titled “combinators”constantCase
Section titled “constantCase”Converts all string path segments to CONSTANT_CASE before lookup.
When to use
Use to bridge camelCase schema keys to SCREAMING_SNAKE_CASE
environment variables.
Details
Numeric segments are left unchanged. String segments use String.configCase
so numeric word groups such as v2 are preserved for environment variable
names. This is a specialization of mapInput.
Example (Resolving camelCase keys to env vars)
import { ConfigProvider } from "effect"
const provider = ConfigProvider.fromEnv({ env: { DATABASE_HOST: "localhost" }}).pipe(ConfigProvider.constantCase)
// path ["databaseHost"] now resolves to env var DATABASE_HOSTSee
mapInput– for arbitrary path transformations
Signature
declare const constantCase: (self: ConfigProvider) => ConfigProviderSince v2.0.0
mapInput
Section titled “mapInput”Transforms the path segments before they reach the underlying store.
When to use
Use when you need to rename, re-case, or otherwise transform config path segments before lookup.
Details
The function f receives the whole path produced by earlier provider
transformations and must return a new path. Lookup path transformations
compose in application order: the existing transformation runs first, then
f runs. For providers composed with orElse, the transformation is
applied to each operand.
Example (Uppercasing path segments)
import { ConfigProvider } from "effect"
const provider = ConfigProvider.fromEnv({ env: { APP_HOST: "localhost" }})
const upper = ConfigProvider.mapInput(provider, (path) => path.map((seg) => (typeof seg === "string" ? seg.toUpperCase() : seg)))See
constantCase– a preset that converts toCONSTANT_CASEnested– for prepending a prefix instead of transforming
Signature
declare const mapInput: { (f: (path: Path) => Path): (self: ConfigProvider) => ConfigProvider (self: ConfigProvider, f: (path: Path) => Path): ConfigProvider}Since v4.0.0
nested
Section titled “nested”Scopes a provider so that all lookups are prefixed with the given path segments.
When to use
Use to namespace config under a prefix like "app" or "database", or
reuse the same provider shape for multiple sub-configs.
Details
Accepts a single string or a full Path array. For providers composed with
orElse, the prefix is applied to each operand. Supports both
data-last and data-first calling conventions.
Gotchas
Ordering matters when composing with mapInput or
constantCase. Later provider transformations run after earlier ones:
a later nested becomes the outer prefix, and a later mapInput sees the
whole path produced by previous transformations.
Example (Nesting under a prefix)
import { ConfigProvider } from "effect"
const provider = ConfigProvider.fromEnv({ env: { APP_HOST: "localhost", APP_PORT: "3000" }})
// Lookups for ["HOST"] now resolve to ["APP", "HOST"]const scoped = ConfigProvider.nested(provider, "APP")See
mapInput– for arbitrary path transformations
Signature
declare const nested: { (prefix: string | Path): (self: ConfigProvider) => ConfigProvider (self: ConfigProvider, prefix: string | Path): ConfigProvider}Since v2.0.0
orElse
Section titled “orElse”Returns a provider that falls back to that when self returns undefined
for a path.
When to use
Use to layer multiple config sources, such as env vars plus a defaults file, or provide partial overrides on top of a base config.
Details
Each provider keeps its own path transformations. If the combined provider
is later transformed with mapInput or nested, the
transformation is applied to both sides.
Gotchas
The fallback only runs when the path is not found (undefined). A
SourceError from self is not caught; it propagates immediately.
Example (Falling back to a default provider)
import { ConfigProvider } from "effect"
const envProvider = ConfigProvider.fromEnv({ env: { HOST: "prod.example.com" }})const defaults = ConfigProvider.fromUnknown({ HOST: "localhost", PORT: "3000" })
const combined = ConfigProvider.orElse(envProvider, defaults)See
layerAdd– install a fallback provider via a Layer
Signature
declare const orElse: { (that: ConfigProvider): (self: ConfigProvider) => ConfigProvider (self: ConfigProvider, that: ConfigProvider): ConfigProvider}Since v2.0.0
constructors
Section titled “constructors”fromDotEnv
Section titled “fromDotEnv”Creates a ConfigProvider by reading and parsing a .env file from the
file system.
When to use
Use to load environment config from a .env file at application startup.
Details
Requires FileSystem in the Effect context. Defaults to reading ".env" in
the current directory; override with { path: "/custom/.env" }.
Returns an Effect that resolves to a ConfigProvider. Fails with a
PlatformError if the file cannot be read.
Example (Loading a .env file)
import { ConfigProvider, Effect } from "effect"
const program = Effect.gen(function* () { const provider = yield* ConfigProvider.fromDotEnv() return provider})See
fromDotEnvContents– parse a.envstring directlyfromEnv– read from the runtime environment
Signature
declare const fromDotEnv: (options?: { readonly path?: string | undefined readonly expandVariables?: boolean | undefined}) => Effect.Effect<ConfigProvider, PlatformError, FileSystem.FileSystem>Since v4.0.0
Creates a ConfigProvider from a raw lookup function.
When to use
Use when implementing a provider backed by a custom store, such as a database, remote API, or in-memory map.
Details
The get callback receives a Path and must return
Effect<Node | undefined, SourceError>. Return undefined when the path
does not exist; fail with SourceError only for actual I/O errors.
Example (Creating a simple in-memory provider)
import { ConfigProvider, Effect } from "effect"
const data: Record<string, string> = { host: "localhost", port: "5432"}
const provider = ConfigProvider.make((path) => { const key = path.join(".") const value = data[key] return Effect.succeed(value !== undefined ? ConfigProvider.makeValue(value) : undefined)})See
fromEnv– pre-built provider for environment variablesfromUnknown– pre-built provider for JSON objects
Signature
declare const make: (get: (path: Path) => Effect.Effect<Node | undefined, SourceError>) => ConfigProviderSince v2.0.0
makeArray
Section titled “makeArray”Creates an Array node representing an indexed container with a known
length.
When to use
Use when you need to describe a JSON array or numerically indexed env vars inside a custom provider.
Details
The optional value allows a node to be both a container and a leaf at the
same time.
Example (Creating an array node)
import { ConfigProvider } from "effect"
const node = ConfigProvider.makeArray(3)// { _tag: "Array", length: 3, value: undefined }See
makeValue– for terminal leavesmakeRecord– for object-like containers
Signature
declare const makeArray: (length: number, value?: string) => NodeSince v4.0.0
makeRecord
Section titled “makeRecord”Creates a Record node representing an object-like container with known
child keys.
When to use
Use when you need to describe a directory or JSON object inside a custom provider.
Details
The optional value allows a node to be both a container and a leaf at the
same time (for example, an env var A=x that also has children A_FOO and
A_BAR).
Example (Creating a record node)
import { ConfigProvider } from "effect"
const node = ConfigProvider.makeRecord(new Set(["host", "port"]))// { _tag: "Record", keys: Set(["host", "port"]), value: undefined }See
makeValue– for terminal leavesmakeArray– for array-like containers
Signature
declare const makeRecord: (keys: ReadonlySet<string>, value?: string) => NodeSince v4.0.0
makeValue
Section titled “makeValue”Creates a Value node representing a terminal string leaf.
When to use
Use when building nodes inside a custom ConfigProvider’s get
callback.
Details
The function returns a new plain object.
Example (Creating a value node)
import { ConfigProvider } from "effect"
const node = ConfigProvider.makeValue("3000")// { _tag: "Value", value: "3000" }See
makeRecord– for object-like containersmakeArray– for array-like containers
Signature
declare const makeValue: (value: string) => NodeSince v4.0.0
layers
Section titled “layers”Provides a layer that installs a ConfigProvider as the active provider for
all downstream effects, replacing any previously installed provider.
When to use
Use to set the config source for an entire application or test suite.
Details
Accepts either a plain ConfigProvider or an Effect that produces one.
When given an Effect, it is evaluated once when the layer is built.
Example (Reading config from a JSON object)
import { Config, ConfigProvider, Effect, Layer } from "effect"
const TestLayer = ConfigProvider.layer(ConfigProvider.fromUnknown({ port: 8080 }))
const program = Effect.gen(function* () { const port = yield* Config.number("port") return port})
// Effect.runSync(Effect.provide(program, TestLayer)) // 8080See
layerAdd– add a provider without replacing the existing one
Signature
declare const layer: <E = never, R = never>( self: ConfigProvider | Effect.Effect<ConfigProvider, E, R>) => Layer.Layer<never, E, Exclude<R, Scope>>Since v4.0.0
layerAdd
Section titled “layerAdd”Creates a Layer that composes a new ConfigProvider with the currently
active one, rather than replacing it.
When to use
Use to add defaults that should only apply when the primary provider has no
value for a path, or override specific keys while keeping the rest from the
existing provider by setting asPrimary: true.
Details
By default, the new provider acts as a fallback and is consulted only when
the current provider returns undefined. Set asPrimary: true to make the
new provider the primary source, with the existing one as fallback.
Example (Adding default values)
import { ConfigProvider } from "effect"
const defaults = ConfigProvider.fromUnknown({ HOST: "localhost", PORT: "3000"})
// The current env provider is tried first; `defaults` is the fallbackconst DefaultsLayer = ConfigProvider.layerAdd(defaults)See
layer– replace the provider entirelyorElse– compose providers without layers
Signature
declare const layerAdd: <E = never, R = never>( self: ConfigProvider | Effect.Effect<ConfigProvider, E, R>, options?: { readonly asPrimary?: boolean | undefined } | undefined) => Layer.Layer<never, E, Exclude<R, Scope>>Since v4.0.0
models
Section titled “models”ConfigProvider (interface)
Section titled “ConfigProvider (interface)”The core interface for loading raw configuration data.
When to use
Use to type-annotate variables that hold a provider or to implement a
custom provider via make.
Details
load(path) is the semantic lookup operation used by the Config module.
It applies provider transformations and composition before consulting the
underlying source. undefined means “not found” and SourceError means the
source itself failed.
See
make– construct a provider from a lookup functionorElse– compose providers with fallback
Signature
export interface ConfigProvider extends Pipeable { /** * Returns the node found at `path`, or `undefined` if it does not exist. * Fails with `SourceError` when the underlying source cannot be read. * * **When to use** * * Use to resolve a path through this provider's path transformations before * reading the backing source. */ readonly load: (path: Path) => Effect.Effect<Node | undefined, SourceError>
/** @internal */ readonly state: ProviderState}Since v2.0.0
Node (type alias)
Section titled “Node (type alias)”A discriminated union describing the shape of a configuration value at a given path.
When to use
Use when implementing a custom ConfigProvider by returning raw
nodes from the get callback passed to make, or when inspecting raw
provider output before schema parsing.
Details
Value is a terminal string leaf. Record is an object-like container
whose immediate child keys are known and may carry an optional co-located
value. Array is an indexed container with a known length and may also
carry an optional co-located value.
See
makeValue– construct aValuenodemakeRecord– construct aRecordnodemakeArray– construct anArraynode
Signature
type Node = | { readonly _tag: "Value" readonly value: string } /** An object; keys are unordered */ | { readonly _tag: "Record" readonly keys: ReadonlySet<string> readonly value: string | undefined } /** An array-like container; length is the number of elements */ | { readonly _tag: "Array" readonly length: number readonly value: string | undefined }Since v4.0.0
Path (type alias)
Section titled “Path (type alias)”An ordered sequence of string or numeric segments that addresses a node in the configuration tree. String segments name object keys; numeric segments index into arrays.
When to use
Use to address raw configuration nodes when implementing or transforming a
ConfigProvider.
Example (A typical config path)
import type { ConfigProvider } from "effect"
const path: ConfigProvider.Path = ["database", "replicas", 0, "host"]Signature
type Path = ReadonlyArray<string | number>Since v4.0.0
SourceError (class)
Section titled “SourceError (class)”Typed error indicating that a configuration source could not be read.
When to use
Use when you need to report that a custom provider’s underlying store is unreachable or produced an I/O error while reading configuration data.
Gotchas
Do not use SourceError for “key not found”. That case is represented by
returning undefined from load.
Example (Failing with a SourceError)
import { ConfigProvider, Effect } from "effect"
const provider = ConfigProvider.make((_path) => Effect.fail(new ConfigProvider.SourceError({ message: "connection refused" })))See
ConfigProvider– the interface whoseloadmay fail with this error
Signature
declare class SourceErrorSince v4.0.0
services
Section titled “services”ConfigProvider
Section titled “ConfigProvider”Context reference for the active raw configuration provider, registered in the context with a
default value of fromEnv(). Because it is a Context.Reference, it is
available without explicit provision; Config schemas automatically resolve
it.
When to use
Use to override the active raw configuration provider for an entire program, or retrieve the current provider inside an Effect.
Example (Providing a custom provider)
import { ConfigProvider, Effect } from "effect"
const provider = ConfigProvider.fromUnknown({ port: 8080 })
const program = Effect.gen(function* () { const current = yield* ConfigProvider.ConfigProvider return current}).pipe(Effect.provideService(ConfigProvider.ConfigProvider, provider))See
layer– install a provider as a LayerlayerAdd– add a fallback provider as a Layer
Signature
declare const ConfigProvider: Context.Reference<ConfigProvider>Since v2.0.0