RepoPilot

How does Drizzle handle schema migrations?

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

Question

How does Drizzle handle schema migrations?

RepoPilot

Based on what's retrievable from this repository's changelogs and the RepoPilot import-graph signals, Drizzle's migration story is split across two packages with distinct responsibilities:

1. drizzle-kit — schema diffing and migration file generation

drizzle-kit is the CLI tool responsible for comparing your TypeScript schema against the database (or a prior snapshot) and producing SQL migration files / DDL statements. Several commands are documented in the changelogs:

  • drizzle-kit generate — the implicit workflow shown throughout the changelogs: you declare tables with pgTable/mysqlTable/etc. (e.g. with check() constraints, views, materialized views), and drizzle-kit serializes that schema into CREATE TABLE/CREATE VIEW/CREATE MATERIALIZED VIEW SQL statements [changelogs/drizzle-kit/0.26.0.md:L50-L107].
  • drizzle-kit export — a newer command (introduced in 0.30.1) that outputs the schema's SQL DDL directly to the console (via --sql) rather than writing migration files, specifically to ease interop with other migration tools like Atlas [changelogs/drizzle-kit/0.30.1.md:L3-L35].
  • drizzle-kit push — referenced as a limitation case: for certain changes (like altering a view's SQL definition), push requires a manual two-step dance of commenting out the view, pushing, then restoring it with the new definition and pushing again, whereas generate handles such changes automatically [changelogs/drizzle-kit/0.26.0.md:L95-L107].
  • Dialect-specific migration strategies — drizzle-kit distinguishes dialects explicitly in config (e.g. dialect: "turso" vs dialect: "sqlite"), because Turso/libsql evolved separate migration strategies starting around 0.25.0, and using the migrate function requires a minimum @libsql/client version [changelogs/drizzle-kit/0.25.0.md:L1-L51].
  • casing configuration — both drizzle-orm (at the connection level, e.g. drizzle('node-postgres', { connection, casing: 'snake_case' })) and drizzle-kit (via defineConfig({ casing: 'snake_case' })) need matching casing config so that generated migrations and runtime queries agree on how camelCase TS fields map to snake_case DB columns [changelogs/drizzle-kit/0.25.0.md:L146-L180].
  • Checks, views, and RLS/Supabase roles are all first-class citizens in the schema that generate/push account for — e.g. check() constraints render as CONSTRAINT ... CHECK (...) [changelogs/drizzle-kit/0.26.0.md:L3-L38], and Supabase-specific predefined roles/tables (declared .existing()) are treated by drizzle-kit purely as external references for FK/policy purposes, not as entities it tries to manage itself [changelogs/drizzle-kit/0.27.0.md:L312-L416].

2. drizzle-orm — applying migrations at runtime

The actual "run these SQL migration files against my live connection" step lives in a per-driver migrate function, historically imported from a dedicated submodule rather than bundled into the main connector, e.g. import { migrate } from 'drizzle-orm-pg/node/migrate' [changelogs/drizzle-orm-pg/0.14.0.md:L1-L9] and import { migrate } from 'drizzle-orm-sqlite/better-sqlite3/migrate' [changelogs/drizzle-orm-sqlite/0.14.1.md:L1-L9]. That same 0.14.0 release also replaced the old Connector class pattern (new PgConnector(client).connect()) with the now-familiar drizzle(client) factory function.

On the architecture side, RepoPilot's import graph flags a direct circular dependency specifically in this migration pathway: `drizzle-kit/src/cli/commands/migrate.ts -> drizzle-kit/src/migrationPreparator.ts -> drizzle-kit/src/serializer/index.ts -> drizzle-kit/src/cli/views.ts -> drizzle-kit/src/utils.ts -> drizzle

Did this answer what you came for?

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.