Migrating from v1
Alchemy v1 uses async/await with top-level await for
orchestration. Alchemy v2 replaces this with Effect generators for
type-safe error handling, composable retries, and declarative
resource wiring.
Your existing async fetch handlers do not need to change — you
can keep them as-is and still get all the benefits of the new engine.
Step 1: Replace the Stack
Section titled “Step 1: Replace the Stack”In v1, you create an app with await alchemy(...) and finalize it
at the end:
// v1 — alchemy.run.tsimport alchemy from "alchemy";import { Worker, Bucket } from "alchemy/cloudflare";
const app = await alchemy("my-app", {});
const bucket = await Bucket("bucket", {});const worker = await Worker("worker", { entrypoint: "./src/worker.ts", bindings: { BUCKET: bucket },});
console.log(worker.url);
await app.finalize();In v2, you export a default Alchemy.Stack and use yield* instead
of await:
// v2 — alchemy.run.tsimport * as Alchemy from "alchemy";import * as Cloudflare from "alchemy/Cloudflare";import * as Effect from "effect/Effect";
export const Bucket = Cloudflare.R2.Bucket("Bucket");
export const Worker = Cloudflare.Worker("Worker", { main: "./src/worker.ts", env: { Bucket },});
export default Alchemy.Stack( "MyApp", { providers: Cloudflare.providers(), state: Cloudflare.state() }, Effect.gen(function* () { const worker = yield* Worker; return { url: worker.url }; }),);Key differences:
entrypointis now calledmainawaitis nowyield*- No more
finalize()— the Stack handles lifecycle automatically
Step 2: Keep your async handler
Section titled “Step 2: Keep your async handler”Your existing Worker runtime code does not need to change. The async
pattern declares bindings on the Worker’s env prop and uses
Cloudflare.InferEnv to type the env object:
export type type WorkerEnv = Cloudflare.InferEnv<any>
WorkerEnv =
Cloudflare.type Cloudflare.InferEnv = /*unresolved*/ any
InferEnv<typeof const Worker: any
Worker>;
export const const Worker: any
Worker = any
Cloudflare.any
Worker("Worker", { main: string
main: "./src/worker.ts", env: { Bucket: any;}
env: { type Bucket: any
Bucket },});Your handler stays the same — just update the type import:
import type { import Env
Env } from "../alchemy.run.ts";import type { import WorkerEnv
WorkerEnv } from "../alchemy.run.ts";
export default { async function fetch(request: Request, env: Env): Promise<Response>
fetch(request: Request
request: interface Request
Request, env: Env
env: import Env
Env) { any
async function fetch(input: string | URL | Request, init?: BunFetchRequestInit): Promise<Response> (+1 overload)
Send a HTTP(s) request
fetch(request: Request
request: var Request: { new (requestInfo: string, init?: RequestInit): Request; new (requestInfo: RequestInit & { url: string; }): Request; new (requestInfo: Request, init?: RequestInit): Request; prototype: Request;}
Request, env: Env
env: import WorkerEnv
WorkerEnv) { const const object: any
object = await env: Env
env.any
BUCKET.any
get("key"); const const object: any
object = await env: Env
env.any
Bucket.any
get("key"); return new var Response: new (body?: Bun.BodyInit | null | undefined, init?: ResponseInit | undefined) => Response
Response(const object: any
object?.any
body ?? null); },};Cloudflare.InferEnv derives a fully typed env object from the
env declared on the Worker. You get type safety on the binding
names and their APIs without using Effect in your runtime code.
Step 3: Deploy
Section titled “Step 3: Deploy”The CLI commands are the same:
alchemy deployalchemy destroyYour v1 state is not compatible with v2. On your first deploy, Alchemy creates new resources. You should destroy your v1 stack first, then deploy with v2.
(Optional) Adopt Effect for runtime code
Section titled “(Optional) Adopt Effect for runtime code”When you’re ready, you can switch to Effect-native Workers. This
gives you typed errors, composable retries, and Effect’s HttpServer
integration.
Instead of declaring env bindings on the resource props, you bind
resources in the Worker’s Init phase using yield*:
import * as import Cloudflare
Cloudflare from "alchemy/Cloudflare";import * as import Effect
Effect from "effect/Effect";import { const HttpServerRequest: Service<HttpServerRequest, HttpServerRequest>
Server-side representation of an incoming HTTP request.
Details
It extends HttpIncomingMessage with request metadata, parsed cookies,
multipart accessors, WebSocket upgrade support, and a modify method for
creating adjusted request views.
Service tag for the active server-side HTTP request.
When to use
Use to access the request currently being handled by HTTP server routes and
middleware.
HttpServerRequest } from "effect/unstable/http/HttpServerRequest";import * as import HttpServerResponse
HttpServerResponse from "effect/unstable/http/HttpServerResponse";import { import Bucket
Bucket } from "./bucket.ts";
export default { async function fetch(request: Request, env: WorkerEnv): Promise<Response>
fetch(request: Request<unknown, CfProperties<unknown>>
request: interface Request<CfHostMetadata = unknown, Cf = CfProperties<CfHostMetadata>>
The Request interface of the Fetch API represents a resource request.
Request, env: WorkerEnv
env: type WorkerEnv = /*unresolved*/ any
WorkerEnv) { const const object: any
object = await env: WorkerEnv
env.any
Bucket.any
get("key"); return new var Response: new (body?: Bun.BodyInit | null | undefined, init?: ResponseInit | undefined) => Response
The Response interface of the Fetch API represents the response to a request.
Response(const object: any
object?.any
body ?? null); },};export default import Cloudflare
Cloudflare.const Worker: <string, Cloudflare.WorkerShape<never>, Cloudflare.WorkerServices | PlatformServices>(id: string, props: InputProps<Cloudflare.WorkerProps>, impl: Effect.Effect<Cloudflare.WorkerShape<never>, ConfigError, Cloudflare.WorkerServices | PlatformServices>) => Effect.Effect<Pipeable & ResourceLike<"Cloudflare.Worker", Cloudflare.WorkerProps<any, Cloudflare.WorkerAssetsConfig | undefined>, { ...;}, { ...;}, Cloudflare.Providers> & { ...;} & { ...;} & Cloudflare.Rpc<...>, never, Cloudflare.Providers | Cloudflare.Container<Id extends string = string>.Application<...>> & Named<...> (+3 overloads)
Worker("Worker", { main?: Input<string | undefined>
Path to the Worker's entry module. Bundled with rolldown before
upload. Mutually exclusive with
script
— provide exactly one.
main: import.
The type of import.meta.
If you need to declare that a given property exists on import.meta,
this type may be augmented via interface merging.
meta.ImportMeta.url: string
file:// url string for the current module.
url }, import Effect
Effect.const gen: <Effect.Effect<Cloudflare.R2.ReadWriteBucketClient, never, Cloudflare.R2.ReadWriteBucket>, { fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, R2Error, HttpServerRequest | RuntimeContext>;}>(f: () => Generator<Effect.Effect<Cloudflare.R2.ReadWriteBucketClient, never, Cloudflare.R2.ReadWriteBucket>, { fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, R2Error, HttpServerRequest | RuntimeContext>;}, never>) => Effect.Effect<...> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = ( total: number, discountRate: number): Effect.Effect<number, DiscountRateError> => discountRate === 0 ? Effect.fail(new DiscountRateError()) : Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() { const transactionAmount = yield* fetchTransactionAmount const discountRate = yield* fetchDiscountRate const discountedAmount = yield* applyDiscount( transactionAmount, discountRate ) const finalAmount = addServiceCharge(discountedAmount) return `Final amount to charge: ${finalAmount}`})
gen(function* () { const const bucket: Cloudflare.R2.ReadWriteBucketClient
bucket = yield* import Cloudflare
Cloudflare.import R2
R2.const ReadWriteBucket: Cloudflare.R2.ReadWriteBucket<never>(args_0: Input<Cloudflare.R2.Bucket> | Effect.Effect<Cloudflare.R2.Bucket, never, never>) => Effect.Effect<Cloudflare.R2.ReadWriteBucketClient, never, Cloudflare.R2.ReadWriteBucket>
ReadWriteBucket(import Bucket
Bucket); return { fetch: Effect.Effect<HttpServerResponse.HttpServerResponse, R2Error, HttpServerRequest | RuntimeContext>
fetch: import Effect
Effect.const gen: <Effect.Effect<HttpServerRequest, never, HttpServerRequest> | Effect.Effect<ObjectBody | null, R2Error, RuntimeContext> | Effect.Effect<string, R2Error, never>, HttpServerResponse.HttpServerResponse>(f: () => Generator<Effect.Effect<HttpServerRequest, never, HttpServerRequest> | Effect.Effect<ObjectBody | null, R2Error, RuntimeContext> | Effect.Effect<...>, HttpServerResponse.HttpServerResponse, never>) => Effect.Effect<...> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = ( total: number, discountRate: number): Effect.Effect<number, DiscountRateError> => discountRate === 0 ? Effect.fail(new DiscountRateError()) : Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() { const transactionAmount = yield* fetchTransactionAmount const discountRate = yield* fetchDiscountRate const discountedAmount = yield* applyDiscount( transactionAmount, discountRate ) const finalAmount = addServiceCharge(discountedAmount) return `Final amount to charge: ${finalAmount}`})
gen(function* () { const const request: HttpServerRequest
request = yield* const HttpServerRequest: Service<HttpServerRequest, HttpServerRequest>
Server-side representation of an incoming HTTP request.
Details
It extends HttpIncomingMessage with request metadata, parsed cookies,
multipart accessors, WebSocket upgrade support, and a modify method for
creating adjusted request views.
Service tag for the active server-side HTTP request.
When to use
Use to access the request currently being handled by HTTP server routes and
middleware.
HttpServerRequest; const const key: string
key = const request: HttpServerRequest
request.HttpServerRequest.url: string
url.String.split(separator: string | RegExp, limit?: number): string[] (+1 overload)
Split a string into substrings using the specified separator and return them as an array.
split("/").Array<string>.pop(): string | undefined
Removes the last element from an array and returns it.
If the array is empty, undefined is returned and the array is not modified.
pop()!; const const object: ObjectBody | null
object = yield* const bucket: Cloudflare.R2.ReadWriteBucketClient
bucket.ReadBucketClient.get(key: string, options?: GetOptions): Effect.Effect<ObjectBody | null, R2Error, RuntimeContext> (+1 overload)
get(const key: string
key); return const object: ObjectBody | null
object ? import HttpServerResponse
HttpServerResponse.const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse
Creates an HTTP response whose body is a string.
text(yield* const object: ObjectBody
object.ObjectBody.text(): Effect.Effect<string, R2Error>
text()) : import HttpServerResponse
HttpServerResponse.const text: (body: string, options?: HttpServerResponse.Options.WithContentType) => HttpServerResponse.HttpServerResponse
Creates an HTTP response whose body is a string.
text("Not found", { status?: number | undefined
status: 404 }); }), }; }),);The Worker resource declaration moves from alchemy.run.ts into the
Worker file itself (using import.meta.url as the main), and the
Stack just yield*-s the imported Worker:
import * as import Alchemy
Alchemy from "alchemy";import * as import Cloudflare
Cloudflare from "alchemy/Cloudflare";import * as import Effect
Effect from "effect/Effect";import import Workervar Worker: Effect.Effect<Pipeable & Alchemy.ResourceLike<"Cloudflare.Worker", Cloudflare.WorkerProps<{ readonly Bucket: any;}, Cloudflare.WorkerAssetsConfig | undefined>, { workerId: string; workerName: string; namespace: string | undefined; logpush: boolean | undefined; url: string | undefined; tags: string[] | undefined; durableObjectNamespaces: Record<string, string>; accountId: string; domains: string[]; crons: string[]; hash?: { assets: string | undefined; bundle: string | undefined; input: string | undefined; metadata?: string | undefined; };}, { ...;}, Cloudflare.Providers> & { ...;} & { ...;} & Alchemy.Rpc<...>, never, Cloudflare.Providers>
Worker from "./src/worker.ts";import { import Bucket
Bucket } from "./src/bucket.ts";
export type type WorkerEnv = { readonly Bucket: any;}
WorkerEnv = import Cloudflare
Cloudflare.type InferEnv<W> = W extends Effect.Effect<infer A, infer _E, infer _R> ? Cloudflare.InferEnv<A> : W extends Cloudflare.Worker<any> ? Cloudflare.InferEnv<Exclude<W["Props"]["env"], undefined>> : { [k in keyof W]: Cloudflare.GetBindingType<W[k]>; }
InferEnv<typeof import Workervar Worker: Effect.Effect<Pipeable & Alchemy.ResourceLike<"Cloudflare.Worker", Cloudflare.WorkerProps<{ readonly Bucket: any;}, Cloudflare.WorkerAssetsConfig | undefined>, { workerId: string; workerName: string; namespace: string | undefined; logpush: boolean | undefined; url: string | undefined; tags: string[] | undefined; durableObjectNamespaces: Record<string, string>; accountId: string; domains: string[]; crons: string[]; hash?: { assets: string | undefined; bundle: string | undefined; input: string | undefined; metadata?: string | undefined; };}, { ...;}, Cloudflare.Providers> & { ...;} & { ...;} & Alchemy.Rpc<...>, never, Cloudflare.Providers>
Worker>; export const const Worker: Effect.Effect<Pipeable & Alchemy.ResourceLike<"Cloudflare.Worker", Cloudflare.WorkerProps<{ readonly Bucket: any;}, Cloudflare.WorkerAssetsConfig | undefined>, { workerId: string; workerName: string; namespace: string | undefined; logpush: boolean | undefined; url: string | undefined; tags: string[] | undefined; durableObjectNamespaces: Record<string, string>; accountId: string; domains: string[]; crons: string[]; hash?: { assets: string | undefined; bundle: string | undefined; input: string | undefined; metadata?: string | undefined; };}, { ...;}, Cloudflare.Providers> & { ...;} & { ...;} & Alchemy.Rpc<...>, never, Cloudflare.Providers>
Worker = import Cloudflare
Cloudflare.const Worker: <{ readonly Bucket: any;}, undefined, never>(id: string, props: Alchemy.InputProps<Cloudflare.WorkerProps<{ readonly Bucket: any;}, undefined>, never> | Effect.Effect<Alchemy.InputProps<Cloudflare.WorkerProps<{ readonly Bucket: any;}, undefined>, never>, ConfigError, never>) => Effect.Effect<Pipeable & Alchemy.ResourceLike<"Cloudflare.Worker", Cloudflare.WorkerProps<{ readonly Bucket: any;}, Cloudflare.WorkerAssetsConfig | undefined>, { workerId: string; ... 9 more ...; hash?: { assets: string | undefined; bundle: string | undefined; input: string | undefined; metadata?: string | undefined; };}, { ...;}, Cloudflare.Providers> & { ...;} & { ...;} & Alchemy.Rpc<...>, never, Cloudflare.Providers> (+3 overloads)
Worker("Worker", { main?: Alchemy.Input<string | undefined>
Path to the Worker's entry module. Bundled with rolldown before
upload. Mutually exclusive with
script
— provide exactly one.
main: "./src/worker.ts", env?: Alchemy.Input<{ readonly Bucket: any;} | undefined>
Environment variables and native Cloudflare Bindings to bind to
the Worker. Accepts:
- Resource references (R2 bucket, KV namespace, D1 database,
another Worker, Durable Object, etc.) — emitted as the
corresponding native binding.
effect/Config values (Config.redacted, Config.string,
Config.number, …) — resolved at deploy time and bound as
secret_text on Cloudflare regardless of the Config
constructor used. See
https://v2.alchemy.run/concepts/secrets Concepts › Secrets and Variables
.
- Literal values — routed by shape:
Redacted<string> →
secret_text, string → plain_text, anything else → json.
In Effect-native Workers you can alternatively yield* a
Config in the Init phase to register the binding implicitly;
env is the only option for async (non-Effect) Workers.
env: { type Bucket: any
Bucket },});
export default import Alchemy
Alchemy.Stack<{ url: Alchemy.Output<string | undefined, never>;}, Alchemy.StackServices | Alchemy.ProviderServices>(stackName: string, options: Alchemy.StackProps<NoInfer<Alchemy.StackServices | Alchemy.ProviderServices>>, eff: Effect.Effect<{ url: Alchemy.Output<string | undefined, never>;}, ConfigError, Alchemy.StackServices | Alchemy.ProviderServices>): Effect.Effect<Alchemy.CompiledStack<{ url: Alchemy.Output<string | undefined, never>;}, any>, ConfigError, never> (+2 overloads)export Stack
Stack( "MyApp", { StackProps<NoInfer<StackServices | ProviderServices>>.providers: Layer<never, never, Alchemy.StackServices>
providers: import Cloudflare
Cloudflare.const providers: () => Layer<unknown, never, any>
Cloudflare providers, bindings, and credentials for Worker-based stacks.
providers(), StackProps<Req>.state: Layer<State, never, Alchemy.StackServices>
state: import Cloudflare
Cloudflare.function state(): Layer<State, never, never>export state
state() }, import Effect
Effect.const gen: <any, { url: Alchemy.Output<string | undefined, never>;}>(f: () => Generator<any, { url: Alchemy.Output<string | undefined, never>;}, never>) => Effect.Effect<{ url: Alchemy.Output<string | undefined, never>;}, unknown, unknown> (+1 overload)
Provides a way to write effectful code using generator functions, simplifying
control flow and error handling.
When to use
Use when you want to write effectful code that looks and behaves like
synchronous code, while still handling asynchronous tasks, errors, and complex
control flow such as loops and conditions.
Generator functions work similarly to async/await but keep errors,
requirements, and interruption in the Effect type. You can yield* values
from effects and return the final result at the end.
Example (Sequencing effects with generators)
import { Data, Effect } from "effect"
class DiscountRateError extends Data.TaggedError("DiscountRateError")<{}> {}
const addServiceCharge = (amount: number) => amount + 1
const applyDiscount = ( total: number, discountRate: number): Effect.Effect<number, DiscountRateError> => discountRate === 0 ? Effect.fail(new DiscountRateError()) : Effect.succeed(total - (total * discountRate) / 100)
const fetchTransactionAmount = Effect.promise(() => Promise.resolve(100))
const fetchDiscountRate = Effect.promise(() => Promise.resolve(5))
export const program = Effect.gen(function*() { const transactionAmount = yield* fetchTransactionAmount const discountRate = yield* fetchDiscountRate const discountedAmount = yield* applyDiscount( transactionAmount, discountRate ) const finalAmount = addServiceCharge(discountedAmount) return `Final amount to charge: ${finalAmount}`})
gen(function* () { const const bucket: any
bucket = yield* import Bucket
Bucket; const const worker: Pipeable & Alchemy.ResourceLike<"Cloudflare.Worker", Cloudflare.WorkerProps<{ readonly Bucket: any;}, Cloudflare.WorkerAssetsConfig | undefined>, { workerId: string; workerName: string; namespace: string | undefined; logpush: boolean | undefined; url: string | undefined; tags: string[] | undefined; durableObjectNamespaces: Record<string, string>; accountId: string; domains: string[]; crons: string[]; hash?: { assets: string | undefined; bundle: string | undefined; input: string | undefined; metadata?: string | undefined; };}, { ...;}, Cloudflare.Providers> & { ...;} & { ...;} & Alchemy.Rpc<...>
worker = yield* import Workervar Worker: Effect.Effect<Pipeable & Alchemy.ResourceLike<"Cloudflare.Worker", Cloudflare.WorkerProps<{ readonly Bucket: any;}, Cloudflare.WorkerAssetsConfig | undefined>, { workerId: string; workerName: string; namespace: string | undefined; logpush: boolean | undefined; url: string | undefined; tags: string[] | undefined; durableObjectNamespaces: Record<string, string>; accountId: string; domains: string[]; crons: string[]; hash?: { assets: string | undefined; bundle: string | undefined; input: string | undefined; metadata?: string | undefined; };}, { ...;}, Cloudflare.Providers> & { ...;} & { ...;} & Alchemy.Rpc<...>, never, Cloudflare.Providers>
Worker; return { url: Alchemy.Output<string | undefined, never>
url: const worker: Pipeable & Alchemy.ResourceLike<"Cloudflare.Worker", Cloudflare.WorkerProps<{ readonly Bucket: any;}, Cloudflare.WorkerAssetsConfig | undefined>, { workerId: string; workerName: string; namespace: string | undefined; logpush: boolean | undefined; url: string | undefined; tags: string[] | undefined; durableObjectNamespaces: Record<string, string>; accountId: string; domains: string[]; crons: string[]; hash?: { assets: string | undefined; bundle: string | undefined; input: string | undefined; metadata?: string | undefined; };}, { ...;}, Cloudflare.Providers> & { ...;} & { ...;} & Alchemy.Rpc<...>
worker.url: Alchemy.Output<string | undefined, never>
url }; }),);Summary
Section titled “Summary”| v1 (async) | v2 (async style) | v2 (Effect style) | |
|---|---|---|---|
| Stack | await alchemy("name") | Alchemy.Stack("name", ...) | Alchemy.Stack("name", ...) |
| Resources | await Bucket(...) | Cloudflare.R2.Bucket(...) | Cloudflare.R2.Bucket(...) |
| Worker entry | entrypoint | main | main: import.meta.url |
| Bindings | bindings: { KEY: resource } | env: { Key: resource } | yield* Binding(ref) |
| Runtime code | async fetch(req, env) | async fetch(req, env) | Effect.gen(function* () { ... }) |
| Lifecycle | await app.finalize() | automatic | automatic |
| Type safety | runtime errors | typed env via InferEnv | full Effect type system |
Where next
Section titled “Where next”- Tutorial — build a full app on v2 from scratch, on Cloudflare or AWS
- Bindings — what replaced v1
bindings: - Cloudflare — the Cloudflare provider hub
- AWS — the AWS provider hub