RepoPilot

How does Drizzle handle schema migrations?

About drizzle-team/drizzle-orm · 2 messages · last activity 8/1/2026

Question

How does Drizzle handle schema migrations?

RepoPilot

Here's a fuller picture of how Drizzle handles schema migrations, combining what I found:

Two complementary approaches:

  1. 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].

  2. Runtime migrate() function in drizzle-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., a SqliteError: near "SCHEMA": syntax error when 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 ... SELECT the data across, DROP the old table, then RENAME the new one back — wrapped in PRAGMA foreign_keys=OFF/ON — separated by --> statement-breakpoint markers [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 the migrate() function with Turso now requires @libsql/client@0.10.0+ [changelogs/drizzle-kit/0.25.0.md:L1-L51].
  • As previously noted, Gel only supports pull, not generate/migrate/push — schema changes for Gel are managed by the native gel CLI 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 via push alone 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 push command limitation in detail?
  • How does the casing config affect migrations?
  • What is Drizzle's plan for "commutative migrations"?

Want to ask your own question?

Open chat for drizzle-team/drizzle-orm

Public 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.