Secrets and env vars
This guide walks through the most common env-var task: take a
secret like OPENAI_API_KEY from your shell or a .env file and
make it available inside a Worker as a secret_text binding —
without leaking it through plans, logs, or the Worker bundle.
The same shape works for non-secret config too — swap
Alchemy.Secret for
Alchemy.Variable and the
binding ships as plain_text / json instead.
Add the env var
Section titled “Add the env var”Put the value somewhere the active
ConfigProvider can
find it. The CLI uses ConfigProvider.fromEnv() by default, so
either an exported shell var or a .env file at the project root
works:
OPENAI_API_KEY=sk-proj-...You don’t need to plumb process.env anywhere — Alchemy.Secret
will read from ConfigProvider for you.
Bind the secret on the Worker
Section titled “Bind the secret on the Worker”Inside the Worker’s init phase, yield* Alchemy.Secret("OPENAI_API_KEY")
records the binding and returns an accessor for the runtime value.
import * as Alchemy from "alchemy"; import * as Cloudflare from "alchemy/Cloudflare"; import * as Effect from "effect/Effect"; import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";
export default Cloudflare.Worker( "Worker", { main: import.meta.path }, Effect.gen(function* () { const apiKey = yield* Alchemy.Secret("OPENAI_API_KEY");
return { fetch: Effect.gen(function* () { return HttpServerResponse.text("Hello, world!"); }), }; }), );The 1-arg form is sugar for
Alchemy.Secret("OPENAI_API_KEY", Config.redacted("OPENAI_API_KEY")).
At deploy time Alchemy resolves the value, hands it to the Worker
provider as a Redacted<string>, and the put-worker payload
declares the binding as type: "secret_text".
Read the value at runtime
Section titled “Read the value at runtime”apiKey is an Effect<Redacted<string>> — an accessor.
yield* it inside fetch to get the value, then unwrap with
Redacted.value when you actually need the string (e.g. an
Authorization header):
import * as Alchemy from "alchemy";import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";import * as Redacted from "effect/Redacted";
export default Cloudflare.Worker( "Worker", { main: import.meta.path }, Effect.gen(function* () { const apiKey = yield* Alchemy.Secret("OPENAI_API_KEY");
return { fetch: Effect.gen(function* () { const key = yield* apiKey; const response = yield* Effect.tryPromise(() => fetch("https://api.openai.com/v1/models", { headers: { Authorization: `Bearer ${Redacted.value(key)}` }, }), ); return HttpServerResponse.text(await response.text()); return HttpServerResponse.text("Hello, world!"); }), }; }),);Redacted keeps the value out of logs and stack traces — the
Bearer … string only exists inside the fetch call.
Override the source
Section titled “Override the source”The 2-arg form replaces Config.redacted(name) with whatever you
want. Useful when the env var on disk has a different name, or when
the value comes from a different system entirely:
import * as Config from "effect/Config";
// Read from a differently-named env varyield* Alchemy.Secret("OPENAI_API_KEY", Config.redacted("MY_OPENAI_KEY"));
// Pull from another resource's outputconst stored = yield* Cloudflare.Secret("ApiKey", { store, value: ... });yield* Alchemy.Secret("OPENAI_API_KEY", stored.value);
// Compute the value with an Effectyield* Alchemy.Secret("OPENAI_API_KEY", Effect.gen(function* () { const raw = yield* fetchKeyFromVault(); return Redacted.make(raw);}));The binding name on the Worker (the key the runtime reads) is always the first argument — the second argument is just where the value comes from.
Non-secret env vars
Section titled “Non-secret env vars”For values that are fine to ship as plain text, swap to
Alchemy.Variable. Strings deploy as plain_text; everything
else deploys as a json binding (Cloudflare parses it back into
the original JS value at runtime):
const host = yield* Alchemy.Variable("HOST", "api.openai.com");const port = yield* Alchemy.Variable("PORT", 443);const flags = yield* Alchemy.Variable("FLAGS", { beta: true });Read them inside fetch the same way — yield* host gives back
the resolved value (no Redacted.value unwrap needed).
You now have:
OPENAI_API_KEYread fromConfigat deploy time- A
secret_textbinding deployed onto the Worker (no plain-text copy in the bundle, plan, or logs) - A typed runtime accessor that returns
Redacted<string>
For the bigger picture — input shapes, the deploy-vs-runtime phases, and when to reach for Cloudflare’s Secrets Store instead — see Concepts › Secrets and Variables.