Plantime and Runtime
Alchemy programs operate in two distinct phases: plantime and runtime. Understanding this split is key to working with platform resources like Workers and Lambda Functions.
The two phases
Section titled “The two phases”| Phase | When it runs | What it does |
|---|---|---|
| Plantime (Init) | During alchemy deploy / plan | Declares resources, resolves bindings, computes the plan |
| Runtime (Exec) | When handling a request | Executes your application logic |
Init and Exec in code
Section titled “Init and Exec in code”Platform resources express this split with the “Effect returning an Effect” pattern:
Cloudflare.Worker( "Worker", { main: import.meta.path }, Effect.gen(function* () { // ─── Init phase (plantime + runtime) ─── const bucket = yield* Cloudflare.R2Bucket.bind(Bucket);
return { // ─── Exec phase (runtime only) ─── fetch: Effect.gen(function* () { const obj = yield* bucket.get("key"); return HttpServerResponse.text(yield* obj.text()); }), }; }),);Init phase code runs in both plantime and runtime:
- At plantime, it registers bindings and resolves dependencies
- At runtime, it initializes SDK clients and prepares the handler
Exec phase code runs only at runtime, when an actual request arrives.
ALCHEMY_PHASE
Section titled “ALCHEMY_PHASE”The current phase is exposed as the ALCHEMY_PHASE environment
variable / config:
| Value | Context |
|---|---|
plan | Default. Running alchemy deploy or alchemy plan. |
dev | Running alchemy dev (local development with hot reload). |
runtime | Running inside a deployed Worker or Lambda Function. |
This is used internally by Alchemy — for example, Binding.Policy
enforces that policy providers are registered at plantime but
gracefully becomes a no-op at runtime (where the policy layer is not
provided).
Why this matters
Section titled “Why this matters”The init/exec split means you can 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 exec already knows which bucket to talk to
Effect.gen(function* () { // Init: runs once at cold start const bucket = yield* Cloudflare.R2Bucket.bind(Bucket); const kv = yield* Cloudflare.KVNamespace.bind(KV);
return { // Exec: runs per request fetch: Effect.gen(function* () { // bucket and kv are already initialized const obj = yield* bucket.get("key"); // ... }), };});