1---
2title: Secrets & env
3description: Bind env vars and secrets to Workers with effect/Config, generate stable tokens with Alchemy.Random, and graduate to Secrets Store when secrets are shared across Workers.
4---
5
6Three ways to get a secret into a Worker, by where the value lives.
7A value from your `.env` that belongs to one Worker —
8`Config.Redacted`. A token your infrastructure mints —
9`Alchemy.Random`. A secret shared across Workers that must rotate
10without a redeploy — [Secrets Store](#graduate-to-secrets-store).
11
12## Bind a secret from .env
13
14Any `Config` yielded in the Worker's Construction phase is read from your
15environment at deploy time, bound to the Worker, and resolved from
16that binding at runtime:
17
18```typescript
19import * as Cloudflare from "alchemy/Cloudflare";
20import * as Config from "effect/Config";
21import * as Effect from "effect/Effect";
22import * as Redacted from "effect/Redacted";
23
24export default Cloudflare.Worker(
25 "Worker",
26 { main: import.meta.url },
27 Effect.gen(function* () {
28 const apiKey = yield* Config.Redacted("API_KEY");
29
30 return {
31 fetch: Effect.gen(function* () {
32 return new Response(`Bearer ${Redacted.value(apiKey)}`);
33 }),
34 };
35 }),
36);
37```
38
39`Config` values bind as `secret_text` regardless of the constructor
40used.
41
42## Unwrap at the call site
43
44The value is a `Redacted<string>` — unwrap with `Redacted.value`
45only where the raw string is needed:
46
47```typescript
48const response = yield* Effect.tryPromise(() =>
49 fetch("https://api.openai.com/v1/models", {
50 headers: { Authorization: `Bearer ${Redacted.value(apiKey)}` },
51 }),
52);
53```
54
55:::caution
56Coercing a `Redacted` into a template literal yields the literal
57string `"<redacted>"` — `` `Bearer ${apiKey}` `` sends
58`Bearer <redacted>`.
59:::
60
61## Plain vars and defaults
62
63Non-secret config uses `Config.String` / `Config.Number`, and any
64combinator works:
65
66```typescript
67const port = yield* Config.Number("PORT").pipe(
68 Config.withDefault(3000),
69);
70```
71
72The raw source value is bound; combinators re-run at runtime against
73it, and a default is never bound. See
74[Secrets and Config](/environments/secrets) for the full semantics.
75
76## Resolve Config in the constructor, not in fetch
77
78`fetch` never runs at deploy time, so a `Config` yielded only there
79is never discovered or bound:
80
81```typescript
82// 🚫 nothing is bound — API_KEY won't exist at runtime
83Effect.gen(function* () {
84 return {
85 fetch: Effect.gen(function* () {
86 const apiKey = yield* Config.Redacted("API_KEY");
87 // ...
88 }),
89 };
90});
91```
92
93Resolve it in the outer `Effect.gen` and reference the `const` from
94the handler:
95
96```typescript
97// ✅ bound in Construction, used in Runtime
98Effect.gen(function* () {
99 const apiKey = yield* Config.Redacted("API_KEY");
100 return {
101 fetch: Effect.gen(function* () {
102 return new Response(Redacted.value(apiKey));
103 }),
104 };
105});
106```
107
108## Async Workers: the env prop
109
110Async (non-Effect) Workers have no Construction phase — declare bindings on
111the `env` prop and type the handler with `InferEnv`:
112
113```typescript
114// alchemy.run.ts
115import * as Cloudflare from "alchemy/Cloudflare";
116import * as Config from "effect/Config";
117
118export const Worker = Cloudflare.Worker("Worker", {
119 main: "./src/worker.ts",
120 env: {
121 API_KEY: Config.Redacted("API_KEY"),
122 HOST: Config.String("HOST"),
123 Bucket, // resource references work the same
124 },
125});
126
127export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
128```
129
130```typescript
131// src/worker.ts
132import type { WorkerEnv } from "../alchemy.run.ts";
133
134export default {
135 async fetch(request: Request, env: WorkerEnv) {
136 return new Response(`Bearer ${env.API_KEY}`);
137 },
138};
139```
140
141Literal values route by shape: `Redacted<string>` → `secret_text`,
142`string` → `plain_text`, anything else → `json`. `Output` values
143(e.g. `Alchemy.makeRandom`) classify by their resolved value with the
144same rules, so a resolved `Redacted` still uploads as `secret_text`.
145
146## Generate tokens with Alchemy.Random
147
148For tokens your infrastructure owns — webhook secrets, bearer
149tokens — mint once with `Random` and it persists in state, so every
150deploy keeps the same value:
151
152```typescript
153import { Random } from "alchemy";
154
155export const AuthTokenValue = Random("AuthTokenValue");
156```
157
15832 hex-encoded bytes by default (pass `bytes` to change); `.text` is
159a `Redacted<string>`, so it never leaks into logs.
160
161## Graduate to Secrets Store
162
163Env vars are baked into one Worker at deploy time. Secrets Store is
164account-level: one secret bound into many Workers, read live at
165runtime so a rotation propagates without redeploying consumers, and
166redacted in the dashboard.
167
168```typescript
169import * as Cloudflare from "alchemy/Cloudflare";
170import * as Effect from "effect/Effect";
171import { AuthTokenValue } from "./auth.ts";
172
173export const Store = Cloudflare.SecretsStore.Store("AuthSecrets");
174
175export const AuthToken = Effect.gen(function* () {
176 const store = yield* Store;
177 const value = yield* AuthTokenValue;
178 return yield* Cloudflare.SecretsStore.Secret("AuthToken", {
179 store,
180 value: value.text,
181 });
182});
183```
184
185```typescript
186export default Cloudflare.Worker(
187 "Api",
188 { main: import.meta.url },
189 Effect.gen(function* () {
190 const authToken = yield* Cloudflare.SecretsStore.ReadSecret(AuthToken);
191
192 return {
193 fetch: Effect.gen(function* () {
194 const token = yield* authToken; // Redacted<string>, read live
195 // ...
196 }),
197 };
198 }).pipe(Effect.provide(Cloudflare.SecretsStore.ReadSecretBinding)),
199);
200```
201
202Reads fail with a typed `SecretError` — handle it with
203`Effect.catchTag`. For the full bearer-token walkthrough — store
204adoption semantics, stack outputs, deploy and curl — see the
205[Secrets Store guide](/cloudflare/security/secrets-store).
206
207## Where next
208
209Related:
210
211- [Secrets Store & auth tokens](/cloudflare/security/secrets-store) —
212 full bearer-token walkthrough.
213- [Turnstile](/cloudflare/security/turnstile) — a real third-party
214 secret key in practice.
215- [Secrets and Config](/environments/secrets) — binding semantics under
216 the hood.
217- [Workers](/cloudflare/compute/workers) — the host every binding lands on.
218- [Secrets & env on AWS](/aws/security/secrets-env) — the same spine on AWS.
219
220Reference:
221
222- [Store API reference](/providers/cloudflare/secretsstore/store)
223- [Secret API reference](/providers/cloudflare/secretsstore/secret)
224- [ReadSecret API reference](/providers/cloudflare/secretsstore/readsecret)