Platform
A Platform is a special kind of Resource that ships runtime code along with its infrastructure. Cloudflare Workers, AWS Lambda Functions, and Cloudflare Containers are all platforms. When you declare a platform you describe both:
- The cloud configuration (memory, region, compatibility flags…)
- The Effect that runs inside it
Both deploy together as one resource. There is no separate “infra project” plus “handler project” — the handler is part of the platform’s declaration.
A Worker, end to end
Section titled “A Worker, end to end”import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";import * as HttpServerResponse from "effect/unstable/http/HttpServerResponse";import { Bucket } from "./bucket.ts";
export default Cloudflare.Worker( "Api", { main: import.meta.path }, Effect.gen(function* () { // Init: bind a resource. The binding is the typed SDK. const bucket = yield* Cloudflare.R2Bucket.bind(Bucket);
return { // Exec: per-request handler. fetch: Effect.gen(function* () { const obj = yield* bucket.get("hello.txt"); return obj ? HttpServerResponse.text(yield* obj.text()) : HttpServerResponse.text("Not found", { status: 404 }); }), }; }),);bucket is the resource itself, exposed as a typed SDK. There’s no
env.BUCKET, no environment variable wiring — the binding is the
client. For the deploy-time mechanics that make this work (IAM, env
injection, typed wrappers) see Binding.
Bindings in action
Section titled “Bindings in action”A binding is what connects a resource to the platform’s runtime. The
syntax yield* SomeResource.bind(target) does three things at once:
- Records the binding on the platform’s deploy plan
- Generates any needed permissions/configuration
- Returns a typed handle you call at runtime
const bucket = yield* Cloudflare.R2Bucket.bind(Bucket);const kv = yield* Cloudflare.KVNamespace.bind(Sessions);
// Inside fetch:yield* bucket.put("key", "value");yield* kv.get("session-id");Every binding obeys the same shape across providers — a Cloudflare R2 binding and an AWS S3 binding are interchangeable from the caller’s point of view.
Automatic IAM (and bindings) on AWS
Section titled “Automatic IAM (and bindings) on AWS”On AWS, every binding maps to specific IAM actions on specific
resources. Alchemy generates least-privilege policies scoped to
the exact resource ARNs — you never write PolicyStatement objects
by hand:
export default AWS.Lambda.Function( "Api", { main: import.meta.path }, Effect.gen(function* () { const getJob = yield* DynamoDB.GetItem.bind(JobsTable); const enqueue = yield* SQS.SendMessage.bind(JobQueue);
return { fetch: Effect.gen(function* () { // The binding alone is enough — IAM is generated automatically. const job = yield* getJob({ Key: { id: { S: "abc" } } }); yield* enqueue({ MessageBody: JSON.stringify(job) }); }), }; }),);The deployed function has exactly:
dynamodb:GetItemonJobsTable.tableArnsqs:SendMessageonJobQueue.queueArn
…and nothing else. If you bind multiple tables to one operation, the
generated policy enumerates each ARN explicitly rather than
falling back to Resource: "*". On Cloudflare, the same call
records a Worker binding instead — the runtime API stays identical.
Writing your own platform
Section titled “Writing your own platform”A platform is a Provider that builds infrastructure and bundles a runtime Effect. Most users won’t need to write one — Worker, Lambda, and Container cover the common cases — but the surface is open.
export const MyPlatform = Platform<MyConfig, Handlers>( "MyPlatform", // ...lifecycle hooks (reconcile / delete) that build the // infrastructure and upload the bundled handler.);See Provider for how lifecycle hooks compose into a Layer, and Resource Lifecycle for when each hook fires.
Effect handlers vs async handlers
Section titled “Effect handlers vs async handlers”Platforms support two styles for the runtime code. Both deploy through the same provider and produce the same artifact.
Effect style — handlers are Effects, with typed errors, composable retries, structured concurrency, and bindings resolved through Effect’s context:
export default Cloudflare.Worker( "Worker", { main: import.meta.path }, Effect.gen(function* () { const bucket = yield* Cloudflare.R2Bucket.bind(Bucket); return { fetch: Effect.gen(function* () { /* ... */ }), }; }),);Async style — handlers are standard async fetch functions.
Bindings are passed as bindings: { ... } props on the resource and
typed via InferEnv:
export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
export const Worker = Cloudflare.Worker("Worker", { main: "./src/worker.ts", bindings: { Bucket },});import type { WorkerEnv } from "../alchemy.run.ts";
export default { async fetch(request: Request, env: WorkerEnv) { const object = await env.Bucket.get("key"); return new Response(object?.body ?? null); },};Pick whichever fits your team. The Effect style unlocks Layers, structured retries, and fine-grained testing; the async style integrates better with existing handler code.
For a deeper look at when init code runs vs when exec code runs (and why), see Plantime and Runtime.