Skip to content

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.

PhaseWhen it runsWhat it does
Plantime (Init)During alchemy deploy / planDeclares resources, resolves bindings, computes the plan
Runtime (Exec)When handling a requestExecutes your application logic

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.

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

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

The init/exec split means you can 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 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");
// ...
}),
};
});