Redacted
Secrets — API keys, passwords, tokens — have a way of escaping into log lines,
error messages, and serialized output. Redacted<A> wraps a sensitive value so
that its string, JSON, and inspection representations all render as
<redacted>, never the underlying secret. The real value is held off to the
side and is only recoverable by explicitly calling Redacted.value, which makes
every point of exposure visible and greppable in your code.
import { Redacted } from "effect"
// Wrap a secret — the value is now protected from accidental loggingconst apiKey = Redacted.make("sk-1234567890abcdef")
console.log(apiKey) // <redacted>console.log(`Bearer ${apiKey}`) // "Bearer <redacted>"console.log(JSON.stringify({ apiKey })) // {"apiKey":"<redacted>"}
// You must opt in, explicitly, to read the real valueconsole.log(Redacted.value(apiKey)) // "sk-1234567890abcdef"Every accidental path — console.log, string interpolation, JSON.stringify —
shows <redacted>. Only the deliberate Redacted.value(apiKey) call exposes the
secret, so a code reviewer can audit exactly where secrets are read.
Creating and reading
Section titled “Creating and reading”import { Redacted } from "effect"
const secret = Redacted.make("my-password")
// Recover the wrapped value (use sparingly, at the point of use)const plain = Redacted.value(secret)You can attach a label, which appears in the rendered output. The label itself is not secret, so it helps you tell redacted values apart in logs without revealing anything:
import { Redacted } from "effect"
const token = Redacted.make("secret-token", { label: "api-token" })
console.log(token) // <redacted:api-token>Value equality
Section titled “Value equality”Redacted compares by the wrapped value, so two redacted wrappers holding the
same secret are Equal.equals — without either of them exposing the secret:
import { Redacted, Equal } from "effect"
console.log(Equal.equals(Redacted.make("abc"), Redacted.make("abc"))) // trueconsole.log(Equal.equals(Redacted.make("abc"), Redacted.make("xyz"))) // falseReading secrets from configuration
Section titled “Reading secrets from configuration”The most common source of a secret is configuration. Config.redacted reads an
environment value and wraps it in a Redacted automatically, so a misconfigured
log statement can never print it. See Configuration for the
full configuration story.
import { Config, ConfigProvider, Effect } from "effect"
const program = Effect.gen(function* () { // apiKey is a Redacted<string>, not a raw string const apiKey = yield* Config.redacted("API_KEY") console.log(apiKey) // <redacted>})
const provider = ConfigProvider.fromEnv({ env: { API_KEY: "sk-1234567890abcdef" }})
Effect.runSync( program.pipe( Effect.provideService(ConfigProvider.ConfigProvider, provider) ))// Output: <redacted>When you actually need to send the key — as an Authorization header, say — call
Redacted.value at that single, intentional point. Everywhere else the value
stays protected.
Wiping a secret
Section titled “Wiping a secret”For extra safety, Redacted.wipeUnsafe deletes the stored value, after which any
further Redacted.value call on that wrapper fails. Use it once a short-lived
secret is no longer needed so it cannot be recovered later.
Redacted in schemas
Section titled “Redacted in schemas”When a secret crosses a boundary that decodes input — an HTTP API
request body, a config file — use Schema.Redacted from the Schema
module so the value is wrapped as part of decoding. That is exactly what
Config.redacted does under the hood (Schema.Redacted(Schema.String)).