1---
2title: Migrations
3description: Generate migrations when your schema changes, review and commit the SQL and snapshots, then deploy from Git.
4---
5
6Migration files belong in Git alongside your schema. Generate and review them
7when the schema changes, commit them, then deploy.
8
9## Generate migrations when the schema changes
10
11```sh
12pnpm exec drizzle-kit generate
13```
14
15Answer any rename prompts and review the generated SQL before applying it.
16`generate` writes migration files; `migrate` applies them to a database.
17
18## Commit the schema and migrations
19
20```sh
21git add src/schema.ts drizzle
22git commit -m "Add schema migration"
23```
24
25Include both the SQL and snapshots:
26
27```text
28drizzle/
29└── 20260919000000_create_users/
30 ├── migration.sql
31 └── snapshot.json
32```
33
34## Deploy the committed migrations
35
36Point the database resource at the checked-in directory:
37
38```typescript
39const db = yield* Cloudflare.D1.Database("app-db", {
40 migrations: "./drizzle",
41});
42```
43
44```sh
45pnpm alchemy deploy
46```
47
48This resource applies pending SQL from `./drizzle`. CI should deploy the
49committed files, not generate a new migration history.
50
51For optional schema-generation automation, see the
52[`Drizzle.Schema` reference](/providers/drizzle/schema). The workflow above
53keeps generation and review separate from deployment.
54
55## Durable Object migrations
56
57Capture SQL during construction; apply it when each object activates:
58
59```text
60Cloudflare.SqlMigrations("./drizzle") → read files into the Worker bundle
61Drizzle.DurableObject({ migrations, ... }) → migrate this object's SQLite database
62```
63
64Deployment does not eagerly migrate every object in the namespace.
65
66### Generate and commit the SQL
67
68```typescript
69// drizzle.config.ts
70import { defineConfig } from "drizzle-kit";
71
72export default defineConfig({
73 dialect: "sqlite",
74 schema: "./src/schema.ts",
75 out: "./drizzle",
76});
77```
78
79Define a table and its [query relations](https://orm.drizzle.team/docs/relations-v2):
80
81```typescript
82// src/schema.ts
83import { defineRelations } from "drizzle-orm";
84import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core";
85
86export const users = sqliteTable("users", {
87 id: integer("id").primaryKey({ autoIncrement: true }),
88 name: text("name").notNull(),
89});
90
91export const relations = defineRelations({ users });
92```
93
94Generate locally, review the SQL, and commit the schema and migrations together:
95
96```sh
97pnpm exec drizzle-kit generate
98git add src/schema.ts drizzle
99git commit -m "Create users table"
100```
101
102CI uses these committed files when deploying.
103
104`SqlMigrations` reads existing files. It does not run drizzle-kit or wait for `Drizzle.Schema`.
105
106### Load the directory in the outer Effect
107
108```typescript
109// src/Users.ts
110import * as Cloudflare from "alchemy/Cloudflare";
111import * as Drizzle from "alchemy/Drizzle/Cloudflare";
112import * as Effect from "effect/Effect";
113import { relations, users } from "./schema.ts";
114
115export class Users extends Cloudflare.DurableObject<Users>()(
116 "Users",
117 Effect.gen(function* () {
118 // Construction: capture SQL without importing .sql or migrations.js.
119 const migrations = yield* Cloudflare.SqlMigrations("./drizzle");
120
121 return Effect.gen(function* () {
122 // Activation: apply pending SQL before exposing methods.
123 const db = yield* Drizzle.DurableObject({ migrations, relations });
124
125 return {
126 addUser: (name: string) => db.insert(users).values({ name }).returning(),
127 listUsers: () => db.query.users.findMany(),
128 };
129 });
130 }),
131) {}
132```
133
134Paths are relative to the Alchemy command's working directory, not `Users.ts`:
135
136```sh
137cd my-app # Contains ./drizzle.
138pnpm alchemy dev
139```
140
141After SQL edits, restart `alchemy dev` or redeploy. SQL directories are not watched.
142
143### Choose a history table
144
145```typescript
146// Default: __alchemy_migrations
147const migrations = yield* Cloudflare.SqlMigrations("./drizzle");
148```
149
150```typescript
151// Custom table
152const migrations = yield* Cloudflare.SqlMigrations({
153 dir: "./drizzle",
154 table: "app_migrations",
155});
156```
157
158### Apply migrations before activation
159
160Each pending file and its history row commit in one native SQLite transaction:
161
162```text
1630001_create_users → SQL + history row commit
1640002_add_email → SQL fails; this file rolls back; activation fails
165next activation → skip 0001; retry 0002
166```
167
168Keep applied files unchanged. Migration failures are initialization defects; the object serves no requests until activation succeeds. Query errors remain typed (`EffectDrizzleQueryError`; transactions can also fail with `SqlError`).
169
170Without Drizzle, use the same engine in the inner Effect:
171
172```typescript
173yield* migrations.apply().pipe(Effect.orDie);
174```
175
176See the [raw SQL example](/sql/effect-sql/migrations#durable-object-migrations) for the complete object.
177
178### Existing Drizzle migrations
179
180Generated `migrations.js` inputs still work with Drizzle's migrator. To switch to Alchemy's engine, replace the import with a construction-time capture:
181
182```diff lang="typescript"
183-import migrations from "../drizzle/migrations.js";
184
185 Effect.gen(function* () {
186+ const migrations = yield* Cloudflare.SqlMigrations("./drizzle");
187 return Effect.gen(function* () {
188 const db = yield* Drizzle.DurableObject({ migrations, relations });
189```
190
191```text
192__drizzle_migrations → copy matching history → __alchemy_migrations
193 leave old table frozen; apply only pending files
194```
195
196:::caution[One-way adoption]
197Keep every historical SQL file and stop using Drizzle's migrator after adoption. Missing matches fail with `MigrationHistoryConflictError`; changing `migrationsTable` alone does not switch engines or convert history.
198:::
199
200For the legacy `meta/_journal.json` layout, upgrade before planning:
201
202```sh
203pnpm exec drizzle-kit up
204# Review the upgraded SQL and snapshots.
205git add drizzle
206git commit -m "Upgrade Drizzle migration layout"
207```
208
209See the [complete Worker example](https://github.com/alchemy-run/alchemy/tree/main/examples/cloudflare-durable-object-sql) for named-object POST and GET routes.
210
211## Existing migration history
212
213If you explicitly choose Alchemy to manage migrations for an existing database,
214see [adopting an existing database](/sql/effect-sql/migrations#adopting-an-existing-database)
215for the one-way history conversion and compatibility checks. Adoption is a
216separate decision from generating and committing migration files.
217
218## Destroy never deletes migrations
219
220Removing `Drizzle.Schema` or destroying the stack leaves the checked-in migration directory intact. Database deletion still follows the database resource's lifecycle.
221
222## Dialects
223
224| `dialect` | Database guide |
225| --- | --- |
226| `"postgres"` (default) | [Neon, PlanetScale, Fly, Hyperdrive](/sql/drizzle/postgres) |
227| `"mysql"` | [PlanetScale MySQL](/sql/drizzle/mysql) |
228| `"sqlite"` | [D1](/sql/drizzle/d1), Durable Objects |
229
230## Where next
231
232- [Postgres](/sql/drizzle/postgres) / [MySQL](/sql/drizzle/mysql) / [D1](/sql/drizzle/d1)
233- [Effect SQL migrations](/sql/effect-sql/migrations)
234- [Schema API reference](/providers/drizzle/schema)