Phases
Every alchemy program runs in two phases — plantime builds the plan, runtime serves requests. Function resources — Workers, Lambdas, Containers — express both in a single program by returning an Effect from inside an Effect.
Init vs runtime
Section titled “Init vs runtime”Cloudflare.Worker( "Worker", { main: import.meta.url }, Effect.gen(function* () { // ─── Init phase ─── const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket);
return { // ─── Runtime phase ─── fetch: Effect.gen(function* () { const obj = yield* bucket.get("key"); return HttpServerResponse.text(yield* obj.text()); }), }; }).pipe(Effect.provide(Cloudflare.R2.ReadWriteBucketBinding)),);| Phase | Code | When it runs |
|---|---|---|
| Init | outer | At plantime and at cold start |
| Runtime | inner | Only inside a deployed handler |
The bucket value is established once during init and captured by
the runtime closure. Init runs at most once per cold start; the
runtime body runs per request with everything already wired up.
The runtime phase is the only place where Alchemy.RuntimeContext
is available. Any Effect whose requirements include RuntimeContext
can only execute inside the runtime closure — the type system
rejects it everywhere else. The next page builds the
colored-function model
on top of this split.
What runs when
Section titled “What runs when”- At plantime, init runs to discover bindings — alchemy needs to know which resources the handler will use so it can wire permissions, env vars, and references.
- At runtime cold start, init runs again — this time inside the
deployed Worker, where the same
bind()calls return live SDK clients backed by the deployed resource. - The runtime body only runs in the deployed handler. It never executes at plantime, so you can put real per-request work there without affecting deploy speed.
ALCHEMY_PHASE
Section titled “ALCHEMY_PHASE”The current phase is exposed as the ALCHEMY_PHASE environment
variable / config key:
| Value | Context |
|---|---|
plan | Default. Running alchemy deploy, alchemy plan, or alchemy dev. |
runtime | Running inside a deployed Worker or Lambda Function. |
Most user code never reads this directly — but providers and Bindings use it internally to behave differently across phases.
ALCHEMY_DEV
Section titled “ALCHEMY_DEV”alchemy dev is a plantime phase, so ALCHEMY_PHASE is plan
during local development just like a regular deploy. To tell whether
you’re running under alchemy dev specifically, read the
ALCHEMY_DEV config key. The CLI sets ALCHEMY_DEV=true on the dev
process; every other entrypoint leaves it unset, so it defaults to
false.
import { ALCHEMY_DEV } from "alchemy";
Effect.gen(function* () { if (yield* ALCHEMY_DEV) { // local-dev-only behavior }});The __ALCHEMY_RUNTIME__ guard
Section titled “The __ALCHEMY_RUNTIME__ guard”A binding’s contract is a Binding.Service; the guarded setup
Effect lives in the implementation Layer you provide, not the
contract. That setup Effect does two things — and the phase decides
how much of it runs:
| Work | Phase | Job |
|---|---|---|
| typed client | both | The lightweight typed SDK that ships with the bundle. |
host.bind(...) wiring | plan | Deploy-time registration of IAM / env / native bindings. |
The deploy-time wiring is fenced behind if (!globalThis.__ALCHEMY_RUNTIME__).
At plantime the guard is open, so calling bind() records what the
function will need. At runtime the global flag is set, so the guard is
skipped and bind() returns just the typed client. The runtime bundle
stays small because the planning branch never executes.
Which wiring the guard protects is decided by the implementation Layer you provided (Bindings).
This is also how alchemy can let you write bucket.get(...) inside a
Worker without bundling AWS / Cloudflare provisioning code: the
provisioning lives behind the guard, never on the runtime path.
Why this matters
Section titled “Why this matters”The init/runtime split lets you write code that:
- Resolves infrastructure references at deploy time — bindings know which bucket ARN, queue URL, etc. to inject.
- Initializes SDK clients once at cold start — not on every request.
- Handles requests with a pre-configured context — the
bucketvariable in the runtime body already knows which resource to talk to.
Next: Layers turns this phase
split into a type-level model — RuntimeContext as a colored
function.
Where next
Section titled “Where next”- Layers —
RuntimeContextas a colored function; infrastructure behind service interfaces. - State Store — where deploy results persist between runs.