1---
2title: Migrating from v1
3description: Migrate your Alchemy v1 (async/await) project to Alchemy v2.
4---
5
6Alchemy v1 uses `async/await` with top-level `await` for
7orchestration. Alchemy v2 replaces this with Effect generators for
8type-safe error handling, composable retries, and declarative
9resource wiring. The v1 documentation lives at
10[v1.alchemy.run](https://v1.alchemy.run).
11
12Your existing `async fetch` handlers **do not need to change** — you
13can keep them as-is and still get all the benefits of the new engine.
14
15## Step 1: Replace the Stack
16
17In v1, you create an app with `await alchemy(...)` and finalize it
18at the end:
19
20```typescript
21// v1 — alchemy.run.ts
22import alchemy from "alchemy";
23import { Worker, Bucket } from "alchemy/cloudflare";
24
25const app = await alchemy("my-app", {});
26
27const bucket = await Bucket("bucket", {});
28const worker = await Worker("worker", {
29 entrypoint: "./src/worker.ts",
30 bindings: { BUCKET: bucket },
31});
32
33console.log(worker.url);
34
35await app.finalize();
36```
37
38In v2, you export a default `Alchemy.Stack` and use `yield*` instead
39of `await`:
40
41```typescript
42// v2 — alchemy.run.ts
43import * as Alchemy from "alchemy";
44import * as Cloudflare from "alchemy/Cloudflare";
45import * as Effect from "effect/Effect";
46
47export const Bucket = Cloudflare.R2.Bucket("Bucket");
48
49export const Worker = Cloudflare.Worker("Worker", {
50 main: "./src/worker.ts",
51 env: { Bucket },
52});
53
54export default Alchemy.Stack(
55 "MyApp",
56 { providers: Cloudflare.providers(), state: Cloudflare.state() },
57 Effect.gen(function* () {
58 const worker = yield* Worker;
59 return { url: worker.url };
60 }),
61);
62```
63
64Key differences:
65
66- `entrypoint` is now called `main`
67- `await` is now `yield*`
68- No more `finalize()` — the Stack handles lifecycle automatically
69
70## Step 2: Keep your async handler
71
72Your existing Worker runtime code does not need to change. The async
73pattern declares bindings on the Worker's `env` prop and uses
74`Cloudflare.InferEnv` to type the `env` object:
75
76```diff lang="typescript"
77// alchemy.run.ts
78+export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
79
80export const Worker = Cloudflare.Worker("Worker", {
81 main: "./src/worker.ts",
82 env: { Bucket },
83});
84```
85
86Your handler stays the same — just update the type import:
87
88```diff lang="typescript"
89// src/worker.ts
90-import type { Env } from "../alchemy.run.ts";
91+import type { WorkerEnv } from "../alchemy.run.ts";
92
93export default {
94- async fetch(request: Request, env: Env) {
95+ async fetch(request: Request, env: WorkerEnv) {
96- const object = await env.BUCKET.get("key");
97+ const object = await env.Bucket.get("key");
98 return new Response(object?.body ?? null);
99 },
100};
101```
102
103`Cloudflare.InferEnv` derives a fully typed `env` object from the
104`env` declared on the Worker. You get type safety on the binding
105names and their APIs without using Effect in your runtime code.
106
107:::tip
108This is the recommended migration path. Get your infrastructure on v2
109first, then optionally adopt Effect for runtime code later.
110:::
111
112## Step 3: Pin your physical names
113
114Your v1 state is not compatible with v2, so v2 starts from an empty
115state store. Without extra steps, the first deploy would create
116brand-new resources alongside your existing ones. Instead, you
117[adopt](/cli/adopting-resources) the resources v1 already deployed —
118no destroy, no downtime.
119
120Adoption works by physical name: when a resource has no prior state,
121the engine looks it up in the cloud by name and takes ownership of
122what it finds. But v1 and v2 derive default names differently, so
123without pinning, v2 would look for names that don't exist.
124
125When you didn't set `name` explicitly, v1 derived it as:
126
127```
128{app}-{id}-{stage}
129```
130
131- `{app}` — the name you passed to `await alchemy("my-app")`
132- `{id}` — the resource's logical ID, e.g. `await Bucket("bucket")`
133- `{stage}` — the `--stage` value; defaults to `$ALCHEMY_STAGE`, then
134 your OS username (`$USER`), then `dev`
135
136So the v1 example at the top of this page, deployed with
137`--stage prod`, created a bucket named `my-app-bucket-prod` and a
138worker named `my-app-worker-prod`. (Resources created inside a nested
139scope get the scope names between `{app}` and `{id}`, and Worker names
140are lowercased.)
141
142Set the `name` prop on each v2 resource to that **exact** name:
143
144```typescript
145export const Bucket = Cloudflare.R2.Bucket("Bucket", {
146 name: "my-app-bucket-prod", // v1's {app}-{id}-{stage}
147});
148
149export const Worker = Cloudflare.Worker("Worker", {
150 main: "./src/worker.ts",
151 name: "my-app-worker-prod", // v1's {app}-{id}-{stage}
152 env: { Bucket },
153});
154```
155
156If you did set `name` in v1, carry that value over unchanged. When in
157doubt, confirm the live names in your v1 state (`.alchemy/` by
158default) or the Cloudflare dashboard.
159
160Because v1 baked the stage into the name, each stage has different
161physical names. If you deploy several stages from the same config,
162compute the name from the stage (e.g. an environment variable your CI
163sets) instead of hardcoding one stage's suffix.
164
165## Step 4: Deploy with `--adopt`
166
167```sh
168alchemy deploy --adopt
169```
170
171For each resource, the engine finds the existing one by its physical
172name and adopts it into v2 state, then reconciles it to the declared
173config. `--adopt` tells the engine to take over resources it cannot
174prove it owns — which is exactly the situation for everything v1
175created. See [Adopting Resources](/cli/adopting-resources) for the
176full ownership rules.
177
178:::tip
179Try the migration on a non-production stage first: deploy your v1
180project to a fresh stage, then run the v2 adoption against it and
181verify everything reconciles cleanly before touching prod.
182:::
183
184:::caution
185After adopting, **do not run `destroy` in your v1 project** — its
186state still points at the same physical resources, so destroying the
187v1 stack would delete the resources v2 now manages. Delete the v1
188state files (the `.alchemy/` directory, or your remote state store
189entries) instead.
190:::
191
192From here on, deploys are normal:
193
194```sh
195alchemy deploy
196```
197
198## (Optional) Adopt Effect for runtime code
199
200When you're ready, you can switch to Effect-native Workers. This
201gives you typed errors, composable retries, and Effect's `HttpServer`
202integration.
203
204Instead of declaring `env` bindings on the resource props, you bind
205resources in the Worker's Construction phase using `yield*`:
206
207```diff lang="typescript"
208// src/worker.ts
209import * as Cloudflare from "alchemy/Cloudflare";
210import * as Effect from "effect/Effect";
211+import { HttpServerRequest } from "effect/http/HttpServerRequest";
212import * as HttpServerResponse from "effect/http/HttpServerResponse";
213import { Bucket } from "./bucket.ts";
214
215-export default {
216- async fetch(request: Request, env: WorkerEnv) {
217- const object = await env.Bucket.get("key");
218- return new Response(object?.body ?? null);
219- },
220-};
221+export default Cloudflare.Worker("Worker",
222+ { main: import.meta.url },
223+ Effect.gen(function* () {
224+ const bucket = yield* Cloudflare.R2.ReadWriteBucket(Bucket);
225+
226+ return {
227+ fetch: Effect.gen(function* () {
228+ const request = yield* HttpServerRequest;
229+ const key = request.url.split("/").pop()!;
230+ const object = yield* bucket.get(key);
231+ return object
232+ ? HttpServerResponse.text(yield* object.text())
233+ : HttpServerResponse.text("Not found", { status: 404 });
234+ }),
235+ };
236+ }),
237+);
238```
239
240The Worker resource declaration moves from `alchemy.run.ts` into the
241Worker file itself (using `import.meta.url` as the `main`), and the
242Stack just `yield*`-s the imported Worker:
243
244```diff lang="typescript"
245// alchemy.run.ts
246import * as Alchemy from "alchemy";
247+import * as Cloudflare from "alchemy/Cloudflare";
248import * as Effect from "effect/Effect";
249+import Worker from "./src/worker.ts";
250+import { Bucket } from "./src/bucket.ts";
251
252-export type WorkerEnv = Cloudflare.InferEnv<typeof Worker>;
253-
254-export const Worker = Cloudflare.Worker("Worker", {
255- main: "./src/worker.ts",
256- env: { Bucket },
257-});
258
259export default Alchemy.Stack(
260 "MyApp",
261 { providers: Cloudflare.providers(), state: Cloudflare.state() },
262 Effect.gen(function* () {
263+ const bucket = yield* Bucket;
264 const worker = yield* Worker;
265 return { url: worker.url };
266 }),
267);
268```
269
270## Summary
271
272| | v1 (async) | v2 (async style) | v2 (Effect style) |
273| ------------ | ----------------------------- | ----------------------------- | ---------------------------------- |
274| Stack | `await alchemy("name")` | `Alchemy.Stack("name", ...)` | `Alchemy.Stack("name", ...)` |
275| Resources | `await Bucket(...)` | `Cloudflare.R2.Bucket(...)` | `Cloudflare.R2.Bucket(...)` |
276| Worker entry | `entrypoint` | `main` | `main: import.meta.url` |
277| Bindings | `bindings: { KEY: resource }` | `env: { Key: resource }` | `yield* Binding(ref)` |
278| Runtime code | `async fetch(req, env)` | `async fetch(req, env)` | `Effect.gen(function* () { ... })` |
279| Lifecycle | `await app.finalize()` | automatic | automatic |
280| Type safety | runtime errors | typed `env` via `InferEnv` | full Effect type system |
281
282## Where next
283
284- Tutorial — build a full app on v2 from scratch, on
285 [Cloudflare](/cloudflare/tutorial/part-1) or [AWS](/aws/tutorial/part-1)
286- [Adopting Resources](/cli/adopting-resources) — the full ownership
287 rules behind `--adopt`
288- [Bindings](/infrastructure-as-effects/binding) — what replaced v1 `bindings:`
289- [Cloudflare](/cloudflare) — the Cloudflare provider hub
290- [AWS](/aws) — the AWS provider hub
291- [v1.alchemy.run](https://v1.alchemy.run) — the v1 documentation