Skip to content

Phases

Every alchemy program operates in two distinct phases:

  • Plantime — the deploy. Runs alchemy deploy, plan, or dev. Produces the resource graph, runs providers, persists state.
  • Runtime — the deployed handler. Runs inside a Worker or Lambda Function whenever a request comes in.

Platform resources express both in a single program by returning an Effect from inside an Effect.

Cloudflare.Worker(
"Worker",
{ main: import.meta.path },
Effect.gen(function* () {
// ─── Init phase ───
const bucket = yield* Cloudflare.R2Bucket.bind(Bucket);
return {
// ─── Exec phase ───
fetch: Effect.gen(function* () {
const obj = yield* bucket.get("key");
return HttpServerResponse.text(yield* obj.text());
}),
};
}),
);
PhaseCodeWhen it runs
InitouterAt plantime and at cold start
ExecinnerOnly at runtime, per request

The bucket value is established once during init and captured by exec. Init runs at most once per cold start; exec runs per request with everything already wired up.

alchemy deploy plantime init() records bindings apply create / update deployed Worker / Lambda cold start runtime init() builds SDK clients fetch() per request
Plantime (top) records bindings and builds the plan. Runtime (bottom) starts on cold start and runs exec per request.
  • 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.
  • Exec only runs at runtime. It never executes at plantime, so you can put real per-request work there without affecting deploy speed.

The current phase is exposed as the ALCHEMY_PHASE environment variable / config key:

ValueContext
planDefault. Running alchemy deploy or alchemy plan.
devRunning alchemy dev (local development with hot reload).
runtimeRunning 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.

A binding has two layers under the hood — and which one runs depends on the phase:

LayerPhaseJob
Binding.ServiceRuntimeThe lightweight typed SDK that ships with the bundle.
Binding.PolicyPlanThe deploy-time logic that emits IAM / env / config.

At plantime the Policy layer is provided, so calling bind() records what the function will need. At runtime the Policy layer is absent — bind() returns just the Service wrapper. The runtime bundle stays small because none of the planning code is included.

This is also how alchemy can let you write bucket.get(...) inside a Worker without bundling AWS / Cloudflare provisioning code: provisioning lives in Policy, not Service.

The init/exec split lets you write code that:

  1. Resolves infrastructure references at deploy time — bindings know which bucket ARN, queue URL, etc. to inject.
  2. Initializes SDK clients once at cold start — not on every request.
  3. Handles requests with a pre-configured context — the bucket variable in exec already knows which resource to talk to.
Effect.gen(function* () {
// Init: runs once per 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* () {
const obj = yield* bucket.get("key");
// ...
}),
};
});

Continue to Binding for the full mechanics of how .bind() wires resources into your handler.