Secrets & env
Three ways to get a secret into a Worker, by where the value lives.
A value from your .env that belongs to one Worker —
Config.Redacted. A token your infrastructure mints —
Alchemy.Random. A secret shared across Workers that must rotate
without a redeploy — Secrets Store.
Bind a secret from .env
Section titled “Bind a secret from .env”Any Config yielded in the Worker’s Construction phase is read from your
environment at deploy time, bound to the Worker, and resolved from
that binding at runtime:
import * as Cloudflare from "@alchemy.run/cloudflare";import * as Config from "effect/Config";import * as Effect from "effect/Effect";import * as Redacted from "effect/Redacted";
export default Cloudflare.Worker( "Worker", { main: import.meta.url }, Effect.gen(function* () { const apiKey = yield* Config.Redacted("API_KEY");
return { fetch: Effect.gen(function* () { return new Response(`Bearer ${Redacted.value(apiKey)}`); }), }; }),);Config values bind as secret_text regardless of the constructor
used, so they’re hidden in the dashboard and in wrangler. For how Config
reaches a runtime on any cloud, with defaults and Redacted values, see
Secrets and Config.
Values from a secrets manager
Section titled “Values from a secrets manager”.env is only the default. A Stack’s secrets option reads the same Config values from a secrets manager instead, like Doppler or Infisical, so production keys never sit in a file on anyone’s machine. Pick the source per stage:
import * as Alchemy from "alchemy";import * as Cloudflare from "@alchemy.run/cloudflare";import * as Doppler from "@alchemy.run/doppler";import * as Effect from "effect/Effect";import Api from "./src/api.ts";
export default Alchemy.Stack( "MyApp", { providers: Cloudflare.providers(), state: Cloudflare.state(), secrets: ({ stage }) => stage === "prod" ? Doppler.Secrets({ project: "my-app", config: "prd" }) : Alchemy.Secrets.DotEnv(), }, Effect.gen(function* () { const api = yield* Api; return { url: api.url }; }),);Your code doesn’t change: Config.Redacted("API_KEY") reads from whichever source the stage uses. See Secret providers for Doppler, Infisical, ordering several sources, and writing your own.
Async Workers: the env prop
Section titled “Async Workers: the env prop”Async (non-Effect) Workers have no Construction phase — declare bindings on
the env prop and type the handler with InferEnv:
import * as Cloudflare from "@alchemy.run/cloudflare";import * as Config from "effect/Config";
export const Worker = Cloudflare.Worker("Worker", { main: "./src/worker.ts", env: { API_KEY: Config.Redacted("API_KEY"), HOST: Config.String("HOST"), Bucket, // resource references work the same },});
export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;import type { WorkerEnv } from "../alchemy.run.ts";
export default { async fetch(request: Request, env: WorkerEnv) { return new Response(`Bearer ${env.API_KEY}`); },};Literal values route by shape: Redacted<string> → secret_text,
string → plain_text, anything else → json. Output values
(e.g. Alchemy.makeRandom) classify by their resolved value with the
same rules, so a resolved Redacted still uploads as secret_text.
Generate tokens with Alchemy.Random
Section titled “Generate tokens with Alchemy.Random”For tokens your infrastructure owns — webhook secrets, bearer
tokens — mint once with Random and it persists in state, so every
deploy keeps the same value:
import { Random } from "alchemy";
export const AuthTokenValue = Random("AuthTokenValue");32 hex-encoded bytes by default (pass bytes to change); .text is
a Redacted<string>, so it never leaks into logs.
Graduate to Secrets Store
Section titled “Graduate to Secrets Store”Env vars are baked into one Worker at deploy time. Secrets Store is account-level: one secret bound into many Workers, read live at runtime so a rotation propagates without redeploying consumers, and redacted in the dashboard.
import * as Cloudflare from "@alchemy.run/cloudflare";import * as Effect from "effect/Effect";import { AuthTokenValue } from "./auth.ts";
export const Store = Cloudflare.SecretsStore.Store("AuthSecrets");
export const AuthToken = Effect.gen(function* () { const store = yield* Store; const value = yield* AuthTokenValue; return yield* Cloudflare.SecretsStore.Secret("AuthToken", { store, value: value.text, });});export default Cloudflare.Worker( "Api", { main: import.meta.url }, Effect.gen(function* () { const authToken = yield* Cloudflare.SecretsStore.ReadSecret(AuthToken);
return { fetch: Effect.gen(function* () { const token = yield* authToken; // Redacted<string>, read live // ... }), }; }).pipe(Effect.provide(Cloudflare.SecretsStore.ReadSecretBinding)),);Reads fail with a typed SecretError — handle it with
Effect.catchTag. For the full bearer-token walkthrough — store
adoption semantics, stack outputs, deploy and curl — see the
Secrets Store guide.
Where next
Section titled “Where next”Related:
- Secrets Store & auth tokens — full bearer-token walkthrough.
- Turnstile — a real third-party secret key in practice.
- Secrets and Config — binding semantics under the hood.
- Workers — the host every binding lands on.
- Secrets & env on AWS — the same spine on AWS.
Reference: