| 1 | --- |
| 2 | title: Migrations |
| 3 | description: Generate migrations when your schema changes, review and commit the SQL and snapshots, then deploy from Git. |
| 4 | --- |
| 5 | |
| 6 | Migration files belong in Git alongside your schema. Generate and review them |
| 7 | when the schema changes, commit them, then deploy. |
| 8 | |
| 9 | ## Generate migrations when the schema changes |
| 10 | |
| 11 | ```sh |
| 12 | pnpm exec drizzle-kit generate |
| 13 | ``` |
| 14 | |
| 15 | Answer 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 |
| 21 | git add src/schema.ts drizzle |
| 22 | git commit -m "Add schema migration" |
| 23 | ``` |
| 24 | |
| 25 | Include both the SQL and snapshots: |
| 26 | |
| 27 | ```text |
| 28 | drizzle/ |
| 29 | └── 20260919000000_create_users/ |
| 30 | ├── migration.sql |
| 31 | └── snapshot.json |
| 32 | ``` |
| 33 | |
| 34 | ## Deploy the committed migrations |
| 35 | |
| 36 | Point the database resource at the checked-in directory: |
| 37 | |
| 38 | ```typescript |
| 39 | const db = yield* Cloudflare.D1.Database("app-db", { |
| 40 | migrations: "./drizzle", |
| 41 | }); |
| 42 | ``` |
| 43 | |
| 44 | ```sh |
| 45 | pnpm alchemy deploy |
| 46 | ``` |
| 47 | |
| 48 | This resource applies pending SQL from `./drizzle`. CI should deploy the |
| 49 | committed files, not generate a new migration history. |
| 50 | |
| 51 | For optional schema-generation automation, see the |
| 52 | [`Drizzle.Schema` reference](/providers/drizzle/schema). The workflow above |
| 53 | keeps generation and review separate from deployment. |
| 54 | |
| 55 | ## Durable Object migrations |
| 56 | |
| 57 | Capture SQL during construction; apply it when each object activates: |
| 58 | |
| 59 | ```text |
| 60 | Cloudflare.SqlMigrations("./drizzle") → read files into the Worker bundle |
| 61 | Drizzle.DurableObject({ migrations, ... }) → migrate this object's SQLite database |
| 62 | ``` |
| 63 | |
| 64 | Deployment does not eagerly migrate every object in the namespace. |
| 65 | |
| 66 | ### Generate and commit the SQL |
| 67 | |
| 68 | ```typescript |
| 69 | // drizzle.config.ts |
| 70 | import { defineConfig } from "drizzle-kit"; |
| 71 | |
| 72 | export default defineConfig({ |
| 73 | dialect: "sqlite", |
| 74 | schema: "./src/schema.ts", |
| 75 | out: "./drizzle", |
| 76 | }); |
| 77 | ``` |
| 78 | |
| 79 | Define a table and its [query relations](https://orm.drizzle.team/docs/relations-v2): |
| 80 | |
| 81 | ```typescript |
| 82 | // src/schema.ts |
| 83 | import { defineRelations } from "drizzle-orm"; |
| 84 | import { integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; |
| 85 | |
| 86 | export const users = sqliteTable("users", { |
| 87 | id: integer("id").primaryKey({ autoIncrement: true }), |
| 88 | name: text("name").notNull(), |
| 89 | }); |
| 90 | |
| 91 | export const relations = defineRelations({ users }); |
| 92 | ``` |
| 93 | |
| 94 | Generate locally, review the SQL, and commit the schema and migrations together: |
| 95 | |
| 96 | ```sh |
| 97 | pnpm exec drizzle-kit generate |
| 98 | git add src/schema.ts drizzle |
| 99 | git commit -m "Create users table" |
| 100 | ``` |
| 101 | |
| 102 | CI 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 |
| 110 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 111 | import * as Drizzle from "alchemy/Drizzle/Cloudflare"; |
| 112 | import * as Effect from "effect/Effect"; |
| 113 | import { relations, users } from "./schema.ts"; |
| 114 | |
| 115 | export 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 | |
| 134 | Paths are relative to the Alchemy command's working directory, not `Users.ts`: |
| 135 | |
| 136 | ```sh |
| 137 | cd my-app # Contains ./drizzle. |
| 138 | pnpm alchemy dev |
| 139 | ``` |
| 140 | |
| 141 | After 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 |
| 147 | const migrations = yield* Cloudflare.SqlMigrations("./drizzle"); |
| 148 | ``` |
| 149 | |
| 150 | ```typescript |
| 151 | // Custom table |
| 152 | const migrations = yield* Cloudflare.SqlMigrations({ |
| 153 | dir: "./drizzle", |
| 154 | table: "app_migrations", |
| 155 | }); |
| 156 | ``` |
| 157 | |
| 158 | ### Apply migrations before activation |
| 159 | |
| 160 | Each pending file and its history row commit in one native SQLite transaction: |
| 161 | |
| 162 | ```text |
| 163 | 0001_create_users → SQL + history row commit |
| 164 | 0002_add_email → SQL fails; this file rolls back; activation fails |
| 165 | next activation → skip 0001; retry 0002 |
| 166 | ``` |
| 167 | |
| 168 | Keep 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 | |
| 170 | Without Drizzle, use the same engine in the inner Effect: |
| 171 | |
| 172 | ```typescript |
| 173 | yield* migrations.apply().pipe(Effect.orDie); |
| 174 | ``` |
| 175 | |
| 176 | See the [raw SQL example](/sql/effect-sql/migrations#durable-object-migrations) for the complete object. |
| 177 | |
| 178 | ### Existing Drizzle migrations |
| 179 | |
| 180 | Generated `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] |
| 197 | Keep 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 | |
| 200 | For the legacy `meta/_journal.json` layout, upgrade before planning: |
| 201 | |
| 202 | ```sh |
| 203 | pnpm exec drizzle-kit up |
| 204 | # Review the upgraded SQL and snapshots. |
| 205 | git add drizzle |
| 206 | git commit -m "Upgrade Drizzle migration layout" |
| 207 | ``` |
| 208 | |
| 209 | See 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 | |
| 213 | If you explicitly choose Alchemy to manage migrations for an existing database, |
| 214 | see [adopting an existing database](/sql/effect-sql/migrations#adopting-an-existing-database) |
| 215 | for the one-way history conversion and compatibility checks. Adoption is a |
| 216 | separate decision from generating and committing migration files. |
| 217 | |
| 218 | ## Destroy never deletes migrations |
| 219 | |
| 220 | Removing `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) |