What is Alchemy?
Alchemy is an Infrastructure-as-Effects framework. It extends Infrastructure-as-Code by combining your cloud resources and the application logic that uses them into a single, type-safe program powered by Effect.
Infrastructure-as-Code vs Infrastructure-as-Effects
Section titled “Infrastructure-as-Code vs Infrastructure-as-Effects”Traditional IaC tools like Terraform, Pulumi, and CDK separate infrastructure definitions from application code. You write your infrastructure in one place and your business logic in another, then wire them together with environment variables, ARNs, and config files.
Alchemy takes a different approach: infrastructure and logic are Effects in the same program.
// alchemy.run.ts — infrastructure and logic in one programimport * as Alchemy from "alchemy";import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";import Worker from "./src/worker.ts";
const Bucket = Cloudflare.R2.Bucket("Bucket");
export default Alchemy.Stack( "MyApp", { providers: Cloudflare.providers(), state: Cloudflare.state() }, Effect.gen(function* () { const bucket = yield* Bucket; const worker = yield* Worker; return { url: worker.url }; }),);// src/worker.ts — the Worker binds the Bucket directlyimport * 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( "Worker", { main: import.meta.url }, Effect.gen(function* () { const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket);
return { fetch: Effect.gen(function* () { const obj = yield* bucket.get("hello.txt"); return obj ? HttpServerResponse.text(yield* obj.text()) : HttpServerResponse.text("Not found", { status: 404 }); }), }; }),);There’s no separate “infra” project — it’s one program.
Type safety
Section titled “Type safety”Alchemy uses TypeScript and Effect’s type system to catch mistakes at compile time. If you forget to provide the right providers for your resources, the compiler tells you:
import * as Alchemy from "alchemy";import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";import * as Layer from "effect/Layer";
export default Alchemy.Stack( "MyApp", { providers: Layer.empty,Error ts(2322) ― }, Effect.gen(function* () { const bucket = yield* Cloudflare.R2.Bucket("Bucket"); }),);This error goes away when you provide Cloudflare.providers(). The
type system ensures every resource has its provider wired up before
you can deploy.
Resources
Section titled “Resources”A Resource is any cloud entity managed by Alchemy — buckets,
databases, queues, workers, IAM roles, DNS records, and more. Each
resource is declared as an Effect and yield*-ed inside a Stack:
const bucket = yield* Cloudflare.R2.Bucket("Bucket");const db = yield* Cloudflare.D1.Database("DB");const queue = yield* AWS.SQS.Queue("Jobs");Resources are just descriptions until they’re yielded. You can declare them in separate files and import them from anywhere:
export const Bucket = Cloudflare.R2.Bucket("Bucket");import { Bucket } from "./bucket.ts";const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket);Bindings
Section titled “Bindings”A Binding connects a resource to a Worker or Lambda. A single binding call hands your handler a typed client and — at deploy time — wires up the permissions, environment variables, and platform bindings that client needs:
const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket);
// later, inside fetch:yield* bucket.put("hello.txt", "world");bucket is the resource presented as a typed SDK. There’s no
env.BUCKET, no BUCKET_NAME lookup, and no hand-written IAM policy
— the binding is the client.
On AWS, the binding emits least-privilege IAM scoped to the exact resource ARN. On Cloudflare, it attaches the native Worker binding (R2, KV, D1, Durable Object). The runtime API is identical, so code written against one consumes the other.
Bindings also enable circular references between resources — Worker A can hold a typed client to Worker B and vice versa — which plain props, a directed acyclic graph, can’t express.
See Bindings for event sources, sinks, and the deploy-time mechanics.
Stacks and stages
Section titled “Stacks and stages”A Stack is a collection of resources deployed together. Every
deploy targets a stage — an isolated environment like dev,
prod, or pr-42:
alchemy deploy # deploys to dev_$USER by defaultalchemy deploy --stage prod # deploys to prodEach stage has its own resources with distinct physical names, so environments never interfere with each other.
Providers
Section titled “Providers”A Provider teaches Alchemy how to manage a resource type. Behind every resource is a provider implementing four lifecycle operations:
read— look up the resource’s live state in the cloud.diff— decide whether a change is a no-op, an update, or a replacement.reconcile— make the cloud match the desired state.delete— remove the resource; idempotent.
The engine drives these in a plan → apply loop: read and
diff build the plan, then reconcile and delete apply it in
dependency order. See Providers and
Resource lifecycle.
Providers are Effect Layers, wired into a Stack with
Cloudflare.providers() or AWS.providers():
Alchemy.Stack( "App", { providers: Cloudflare.providers(), state: Cloudflare.state() },);The type system checks the wiring — using a Cloudflare resource
without Cloudflare.providers(), or providing the wrong cloud’s
providers, is a compile-time error. To support a new cloud or
third-party API, see Writing a Custom Resource
Provider.
Two styles: Effect and async
Section titled “Two styles: Effect and async”Alchemy supports two styles for writing Workers:
Effect style — your runtime code is an Effect, with typed errors, composable retries, and bindings resolved through the Effect system:
export default Cloudflare.Worker( "Worker", { main: import.meta.url }, Effect.gen(function* () { const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket); return { fetch: Effect.gen(function* () { // Effect-native runtime code }), }; }),);Async style — your runtime code is a standard async fetch
handler. Bindings are passed as props and you get a typed env
object via InferEnv:
export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
export const Worker = Cloudflare.Worker("Worker", { main: "./src/worker.ts", env: { 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); },};Both styles use the same infrastructure declarations, the same CLI, and the same deployment pipeline. Choose whichever fits your team.
Where next
Section titled “Where next”- Getting started — install and deploy in two minutes
- Pick your provider: Cloudflare or AWS
- Migrating from v1 — upgrade from Alchemy v1