How does Drizzle handle schema migrations?
How does Drizzle handle schema migrations?
About drizzle-team/drizzle-orm · 2 messages · last activity 8/1/2026
Here's a fuller picture of how Drizzle handles schema migrations, combining what I found:
Two complementary approaches:
-
drizzle-kit(CLI companion) — generates SQL migration files from your TypeScript schema, or applies schema changes directly to the database. As the README puts it: "Drizzle comes with a powerful Drizzle Kit CLI companion for you to have hassle-free migrations. It can generate SQL migration files for you or apply schema changes directly to the database" [README.md:L29-L35]. -
Runtime
migrate()function indrizzle-orm— applies the generated SQL migration files against a live database connection at runtime. This was historically exposed as a separate import (e.g.drizzle-orm-pg/node/migrate,drizzle-orm-sqlite/better-sqlite3/migrate) [changelogs/drizzle-orm-pg/0.14.0.md:L1-L9] [changelogs/drizzle-orm-sqlite/0.14.1.md:L1-L9]. Bugs affecting this path have been fixed over time — e.g., aSqliteError: near "SCHEMA": syntax errorwhen running migrations was fixed in both 0.12.0-beta.19 and reconfirmed fixed alongside an async-driver fix in 0.12.0-beta.20 [changelogs/drizzle-orm-sqlite/0.12.0-beta.19.md:L1-L4] [changelogs/drizzle-orm-sqlite/0.12.0-beta.20.md:L1-L5].
Migration file generation details:
- For SQLite, when a change isn't natively supported by
ALTER TABLE(e.g., changing a column's type), drizzle-kit used to just emit a comment telling you to do it manually. As of the 0.25.0 changelog, it instead generates a full "rebuild" migration: create a new__new_<table>table,INSERT ... SELECTthe data across,DROPthe old table, thenRENAMEthe new one back — wrapped inPRAGMA foreign_keys=OFF/ON— separated by--> statement-breakpointmarkers [changelogs/drizzle-kit/0.25.0.md:L54-L88].
Dialect-specific migration strategies:
- Turso/libsql users had to split off into their own
dialect: "turso"(rather than sharing the plain"sqlite"dialect) starting in drizzle-kit 0.25.0, since Turso's migration strategy is evolving separately, and using themigrate()function with Turso now requires@libsql/client@0.10.0+[changelogs/drizzle-kit/0.25.0.md:L1-L51]. - As previously noted,
Gelonly supportspull, notgenerate/migrate/push— schema changes for Gel are managed by the nativegelCLI instead [changelogs/drizzle-kit/0.30.5.md:L3-L30]. - PostgreSQL's DDL generation was changed in 0.30.0 to drop
IF NOT EXISTS/$DO-style defensive statements so that migrations correctly fail rather than silently no-op when an object already exists. This is described as the first step toward a bigger planned migration overhaul featuring "commutative migrations, a revised folder structure, and enhanced collaboration capabilities," plus better Xata support and CockroachDB compatibility [changelogs/drizzle-kit/0.30.0.md:L1-L7]. - Views (including PostgreSQL materialized views) are fully supported by
generate, but changing a view's SQL definition viapushalone requires a manual two-step dance (comment out the view, push, restore with new definition, push again) [changelogs/drizzle-kit/0.26.0.md:L96-L107].
So in short: you author schema in TypeScript, drizzle-kit generate (or push) turns schema diffs into SQL migration files (or applies them directly), and drizzle-orm's migrate() function executes those SQL files against your database at runtime — with dialect-specific quirks/limitations for SQLite rebuilds, Turso, Gel, and views.
FOLLOW_UPS:
- What is drizzle-kit's
pushcommand limitation in detail? - How does the
casingconfig affect migrations? - What is Drizzle's plan for "commutative migrations"?
Want to ask your own question?
Open chat for drizzle-team/drizzle-ormPublic Q&A. Generated by RepoPilot from the actual source of drizzle-team/drizzle-orm. AI answers can be incomplete or stale — verify before relying on them.