Dev Encyclopedia
ArticlesTools

Get notified when new content drops

No spam. Just new articles, tools, and updates — straight to your inbox.

Dev Encyclopedia

A reference for builders

Content

  • Articles
  • Tools
  • Contact

Connect

  • support@devencyclopedia.com
  • RSS Feed

© 2026 Dev Encyclopedia

Privacy PolicyTermsDisclaimer
  1. Home
  2. /Blog
  3. /Drizzle ORM Migrations: A Practical drizzle-kit Guide
databases9 min read

Drizzle ORM Migrations: A Practical drizzle-kit Guide

Learn the full Drizzle ORM migration workflow: push vs migrate, drizzle-kit setup, Turso/libSQL config, team conflicts, and production best practices.

By Dev EncyclopediaPublished May 30, 2026
On this page

On this page

  • push vs migrate: The Key Distinction
  • Setting Up drizzle-kit
  • Install and configure drizzle-kit
  • Define schema and generate migrations
  • Turso/libSQL-specific setup
  • Run migrations in production
  • Troubleshooting
  • drizzle-kit Commands Reference
  • Frequently Asked Questions

If you've added Drizzle to your Next.js project and gotten confused by push vs migrate, or you're not sure how to handle migrations in a team without conflicts, this guide covers the actual workflow. Not just the happy path.

This is the workflow DevEncyclopedia used when migrating its own production database from MongoDB to Turso. The Turso-specific setup reflects what we actually deployed.

push vs migrate: The Most Important Distinction

Drizzle is split into two packages: drizzle-orm (the runtime) and drizzle-kit (the CLI). Your schema is TypeScript. drizzle-kit reads it and does one of two things:

drizzle-kit pushdrizzle-kit generate + migrate
Creates SQL filesNoYes — committed to Git
Tracks historyNoYes — __drizzle_migrations table
Safe for productionNeverYes
SpeedInstantFast
Best forLocal dev iterationAll team and production changes
bash
# Development — fast, no files
npx drizzle-kit push

# Production — generate first, review SQL, then apply
npx drizzle-kit generate
npx drizzle-kit migrate

🚫 Danger

Never use drizzle-kit push in production. It applies changes immediately with no confirmation, has no migration history, and can silently drop columns if your schema removes them.

Setting Up drizzle-kit

  1. 1

    Install and configure drizzle-kit

    Create drizzle.config.ts in your project root. This example uses PostgreSQL. Turso config is in Step 3:

    typescript — drizzle.config.ts
    import { defineConfig } from 'drizzle-kit';
    
    export default defineConfig({
      schema: './src/db/schema.ts',
      out: './migrations',
      dialect: 'postgresql',
      dbCredentials: {
        url: process.env.DATABASE_URL!,
      },
    });

    💡 Tip

    Add these npm script shortcuts: "db:generate": "drizzle-kit generate", "db:migrate": "drizzle-kit migrate", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio".

  2. 2

    Define your schema and generate the first migration

    typescript — src/db/schema.ts
    import { pgTable, serial, text, integer, timestamp } from 'drizzle-orm/pg-core';
    
    export const users = pgTable('users', {
      id: serial('id').primaryKey(),
      name: text('name').notNull(),
      email: text('email').notNull().unique(),
      createdAt: timestamp('created_at').defaultNow(),
    });
    
    export const posts = pgTable('posts', {
      id: serial('id').primaryKey(),
      title: text('title').notNull(),
      authorId: integer('author_id').references(() => users.id),
      publishedAt: timestamp('published_at'),
    });
    bash
    npm run db:generate
    # Creates ./migrations/0001_initial.sql

    ℹ Info

    Commit the generated SQL file to Git. It is the audit trail for every schema change in the project's history. Review it in pull requests the same way you review code.

  3. 3

    Turso/libSQL-specific setup

    If you're using Turso, the dialect, credentials, and column types all change from PostgreSQL:

    typescript — drizzle.config.ts — Turso
    import { config } from 'dotenv';
    import { defineConfig } from 'drizzle-kit';
    
    config({ path: '.env.local' }); // drizzle-kit does not auto-load .env.local
    
    export default defineConfig({
      schema: './src/db/schema.ts',
      out: './migrations',
      dialect: 'turso',
      dbCredentials: {
        url: process.env.TURSO_DATABASE_URL!,
        authToken: process.env.TURSO_AUTH_TOKEN!,
      },
    });
    • Install the Turso driver: npm install @libsql/client
    • Use sqliteTable from drizzle-orm/sqlite-core instead of pgTable
    • Use integer('id').primaryKey({ autoIncrement: true }) instead of serial for auto-increment IDs

    ⚠ Warning

    drizzle-kit does not automatically read .env.local. That is a Next.js feature, not a drizzle-kit feature. Add the dotenv import shown above or you'll get url: undefined errors when running any drizzle-kit command.

  4. 4

    Run migrations in production

    Run migrations as part of your deployment process, not on application startup:

    bash — Vercel — add to Build Command
    npm run db:migrate && next build
    bash — Cloudflare — run before wrangler deploy
    npm run db:migrate
    npx wrangler deploy

    ℹ Info

    Running db:migrate twice is safe. Drizzle tracks applied migrations in a __drizzle_migrations table by content hash and skips already-applied ones. Never edit applied migration files — the hash will no longer match and future runs will fail.

Troubleshooting

Edge cases you'll hit when working in a team:

  1. 1

    Handling team migration conflicts

    When two branches both add schema changes and merge, you can end up with two files both numbered 0002_something.sql. Fix by regenerating from the resolved schema:

    bash
    # 1. Resolve merge conflicts in schema.ts
    # 2. Delete the conflicting migration files
    # 3. Regenerate from the resolved schema
    npm run db:generate
    # Creates one clean migration covering all merged changes
  2. 2

    Reverting a migration

    Drizzle does not generate automatic rollback files. To reverse a migration, write the reverse SQL by hand as a new forward migration:

    • Additive changes (adding a table, adding a nullable column) are safe and rarely need reverting
    • Destructive changes (dropping a column, changing a type) require a hand-written reverse migration and should always be tested on staging first
    • Always export a database backup before any destructive production migration

drizzle-kit Commands Reference

CommandWhat it doesWhen to use
drizzle-kit generateCreates SQL migration files from schema diffEvery time you change schema.ts
drizzle-kit migrateApplies pending migration files to the databaseBefore every deployment
drizzle-kit pushSyncs schema directly without filesDevelopment only — never production
drizzle-kit studioOpens Drizzle Studio GUI in your browserBrowsing/editing data locally
drizzle-kit introspectGenerates schema.ts from an existing databaseMigrating from Prisma or another ORM

Frequently Asked Questions

What is the difference between drizzle-kit push and drizzle-kit migrate?
pushgenerate + migrate
SQL files created?NoYes
History tracked?NoYes
Safe for production?NeverYes

Use push in development for speed. Use generate + migrate for every production change.

Why does drizzle-kit push fail with 'url: undefined' on Turso?

drizzle-kit does not automatically load .env.local. Add this to the top of drizzle.config.ts:

typescript
import { config } from 'dotenv';
config({ path: '.env.local' });

Also install dotenv as a dev dependency: npm install -D dotenv.

How do I handle migration conflicts when multiple developers change the schema?
  1. Resolve the conflict in schema.ts first — get both sets of changes into one clean schema
  2. Delete the conflicting migration files (both 0002_branch_a.sql and 0002_branch_b.sql)
  3. Run drizzle-kit generate to produce one clean migration covering all merged changes
  4. Commit migration files to Git and review them in pull requests the same way you review code
When should I run drizzle-kit generate?

Every time you change schema.ts. Commit the generated migration file alongside the schema change. In CI you can add a check that fails the build when a schema change exists without a corresponding migration file, preventing deployments with unapplied changes.

I'm migrating from Prisma. How is Drizzle's migration workflow different?
PrismaDrizzle
Generate + apply in one command?Yes (migrate dev)No — separate generate and migrate
Shadow database needed?YesNo
Migration seeding?YesNo
Schema languagePrisma Schema Language (.prisma)TypeScript

Drizzle gives you more visibility and control. You are responsible for running generate before each deployment — it doesn't happen automatically.

The Drizzle migration workflow is straightforward once the push vs migrate distinction clicks: push to move fast in development, generate + migrate to make changes reviewable and auditable before touching production.

Commit your migration files, export a backup before destructive changes, and wire db:migrate into your deployment pipeline. That's the entire production discipline.

Related Articles

typescript

TypeScript 7 (Project Corsa): What Next.js Devs Need to Know

TypeScript 7 rewrites the compiler in Go for 10x faster builds. Here's what it means for your Next.js project and what to do right now.

May 30, 2026·7 min read
nextjs

How to Use Environment Variables in Next.js (Without Leaking Them to the Browser)

Learn how to use .env files in Next.js correctly. Understand NEXT_PUBLIC_, avoid common mistakes, and set variables in Vercel and Cloudflare.

May 30, 2026·7 min read
nextjs

Husky + Prettier + lint-staged Setup for Next.js

Set up Husky v9, Prettier, and lint-staged in your Next.js project. Step-by-step guide covering pre-commit hooks with the correct 2026 config.

May 30, 2026·8 min read

On this page

  • push vs migrate: The Key Distinction
  • Setting Up drizzle-kit
  • Install and configure drizzle-kit
  • Define schema and generate migrations
  • Turso/libSQL-specific setup
  • Run migrations in production
  • Troubleshooting
  • drizzle-kit Commands Reference
  • Frequently Asked Questions