1---
2title: Secrets & Config
3description: Use effect/Config to read env vars at Construction time and have Alchemy automatically bind them onto the deploy target.
4---
5
6Alchemy integrates with [effect/Config](https://effect.website/docs/configuration)
7to automatically bind env vars to your Worker or Lambda environment ([Runtime](/infrastructure-as-effects/runtime)).
8Bound values come from the env of whoever runs the deploy, so they naturally
9vary per [stage](/environments/stages) and [profile](/environments/profiles).
10
11Where those values come from is the stack's business. By default
12that is `.env` plus the shell; a stack can instead read them from
13[Doppler](/environments/doppler) or [Infisical](/environments/infisical),
14and layer several sources in a chosen order.
15See [Secret providers](/environments/secret-providers). This page is
16about what happens after a value is read.
17
18## Bind a secret to your Worker
19
20Any `Config` value that is evaluated with `yield*` in the [Construction phase](/infrastructure-as-effects/phases)
21of (for example) a Worker, is automatically bound to its environment
22at deploy time. It is always bound as a secret (`secret_text` on
23Cloudflare) regardless of which `Config` constructor you use.
24
25```typescript
26import * as Cloudflare from "alchemy/Cloudflare";
27import * as Config from "effect/Config";
28import * as Effect from "effect/Effect";
29import * as Redacted from "effect/Redacted";
30
31export default Cloudflare.Worker(
32 "Worker",
33 { main: import.meta.url },
34 Effect.gen(function* () {
35 const apiKey = yield* Config.Redacted("API_KEY");
36 // apiKey is Redacted<string> — usable here in Construction AND captured as a binding
37
38 return {
39 fetch: Effect.gen(function* () {
40 return new Response(`Bearer ${Redacted.value(apiKey)}`);
41 }),
42 };
43 }),
44);
45```
46
47:::tip
48See [Phases](/infrastructure-as-effects/phases) for why Construction and Runtime run separately.
49:::
50
51## Use the value during Construction
52
53Unlike an [Output](/infrastructure-as-code/outputs), the value can be used immediately
54within the Construction phase. For example, to initialize a client:
55
56```typescript
57export default Cloudflare.Worker(
58 "Worker",
59 { main: import.meta.url },
60 Effect.gen(function* () {
61 const apiKey = yield* Config.Redacted("OPENAI_API_KEY");
62 const client = createOpenAI(Redacted.value(apiKey));
63
64 return {
65 fetch: Effect.gen(function* () {
66 const reply = yield* Effect.tryPromise(() => client.chat(/* ... */));
67 return new Response(reply);
68 }),
69 };
70 }),
71);
72```
73
74
75## Transformations
76
77Any combinator works — `withDefault`, `orElse`, `mapEffect`, and so
78on. What gets bound is the *source* value (the raw env var), not the
79transformed result; the combinators run again at runtime against that
80source.
81
82```typescript
83const port = yield* Config.Number("PORT").pipe(
84 Config.withDefault(3000),
85);
86```
87
88- If `PORT` is set, its raw value is bound. At runtime `Config.Number("PORT")`
89 reads it from the binding and the combinators re-apply.
90- If `PORT` is not set, nothing is bound. At runtime the source is
91 still empty and `Config.withDefault(3000)` produces `3000` again.
92
93Because a default is never bound, its code runs in both phases — keep
94it deterministic, so both phases evaluate to the same value.
95
96## Footgun: don't `yield*` Config at runtime
97
98If you only `yield*` the `Config` in `fetch`, it will not be bound to the environment
99because `fetch` does not run during deployment, so the engine never discovers it.
100
101```typescript
102export default Cloudflare.Worker(
103 "Worker",
104 { main: import.meta.url },
105 Effect.gen(function* () {
106 return {
107 fetch: Effect.gen(function* () {
108 // 🚫 nothing is bound to the Worker — API_KEY won't exist at runtime
109 const apiKey = yield* Config.Redacted("API_KEY");
110 // ...
111 }),
112 };
113 }),
114);
115```
116
117Always resolve the `Config` in the outer `Effect.gen` (Construction) — even
118if you only need the value inside `fetch`. Capture it in a `const`,
119and reference that `const` from the runtime body:
120
121```typescript
122// ✅ bound in Construction, used in Runtime
123Effect.gen(function* () {
124 const apiKey = yield* Config.Redacted("API_KEY");
125 return {
126 fetch: Effect.gen(function* () {
127 return new Response(Redacted.value(apiKey));
128 }),
129 };
130});
131```
132
133Once a `Config` has been resolved during Construction, re-yielding the same
134`Config` anywhere at runtime — inside `fetch`, deep in a nested
135effect, or in a service layer — resolves it from the binding and
136produces the same value.
137
138## Async Workers — bind via `env`
139
140Async (non-Effect) Workers don't have a constructor `Effect.gen` to
141`yield*` into, so you put the `Config` on the resource's `env`
142prop instead. Alchemy resolves each `Config` at deploy time and
143records the appropriate binding — same end result as the `yield*`
144form, just declared statically:
145
146```typescript
147// alchemy.run.ts
148import * as Cloudflare from "alchemy/Cloudflare";
149import * as Config from "effect/Config";
150
151export const Worker = Cloudflare.Worker("Worker", {
152 main: "./src/worker.ts",
153 env: {
154 API_KEY: Config.Redacted("API_KEY"),
155 HOST: Config.String("HOST"),
156 Bucket, // resource references work the same
157 },
158});
159
160export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
161```
162
163```typescript
164// src/worker.ts
165import type { WorkerEnv } from "../alchemy.run.ts";
166
167export default {
168 async fetch(request: Request, env: WorkerEnv) {
169 return new Response(`Bearer ${env.API_KEY}`);
170 },
171};
172```
173
174`env.API_KEY` is the resolved string at runtime — Cloudflare hands
175the `secret_text` value to the async handler directly, no
176`Redacted.value` unwrap. The always-binds-as-secret rule and
177transformation semantics from the previous sections still apply.
178
179For the step-by-step "wire up `OPENAI_API_KEY` from `.env`" walk,
180see [Secrets & env on Cloudflare](/cloudflare/security/secrets-env) or
181[on AWS](/aws/security/secrets-env).
182
183## Where next
184
185- [Secret providers](/environments/secret-providers) — where `Config` values come from and in what order.
186- [Local development](/environments/local-development) — `alchemy dev`: local code, real cloud resources.
187- [Secrets & env on Cloudflare](/cloudflare/security/secrets-env) — wire a real key from `.env` into a Worker.
188- [Secrets & env on AWS](/aws/security/secrets-env) — the same walk for Lambda.