Dev Encyclopedia
ArticlesToolsContactAbout

Get notified when new content drops

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

Dev Encyclopedia

A reference for builders

Dev.to
Discord
WhatsApp Channel
daily.dev
Hashnode
X

Content

  • Articles
  • Tools
  • About
  • Contact

Connect

  • support@devencyclopedia.com
  • RSS Feed

Legal

  • Privacy Policy
  • Terms of Service
  • Disclaimer

© 2026 Dev Encyclopedia

Back to top ↑
  1. Home
  2. /Blog
  3. /Drizzle ORM Migrations: A Practical drizzle-kit Guide
databases20 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.

Zeeshan Tofiq
Zeeshan Tofiq
May 30, 2026
On this page

On this page

  • push vs migrate: The Key Distinction
  • How Drizzle Tracks Applied Migrations
  • Setting Up drizzle-kit
  • Install and configure drizzle-kit
  • Define schema and generate migrations
  • Turso/libSQL-specific setup
  • Run migrations in production
  • Running Migrations From Code
  • Troubleshooting
  • Common drizzle-kit Errors and What They Mean
  • Switching from push to migrate
  • Rename prompts and data loss
  • Migrations in CI/CD
  • Zero-Downtime Schema Changes
  • 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 (the __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.

How Drizzle Tracks Applied Migrations

Most of the confusion around generate comes from a wrong mental model. drizzle-kit generate does not look at your database. It compares your TypeScript schema against a snapshot of the last generated state and writes the difference out as SQL.

That snapshot lives next to your migrations. Every generate run produces two artifacts: a numbered .sql file and a JSON snapshot describing the full shape of your schema at that moment. The next run diffs against the snapshot, which is why generate works offline with no database credentials at all.

bash — migrations/ folder layout
migrations/
  0000_lively_iron_man.sql       # the statements that get executed
  0001_wide_stark_industries.sql
  meta/
    _journal.json                # ordered index of every migration
    0000_snapshot.json           # schema shape after 0000
    0001_snapshot.json           # schema shape after 0001

Everything in that folder belongs in Git, including meta/. Developers who add migrations/meta to .gitignore because it looks like build output end up with duplicate migrations the first time a teammate runs generate: with no snapshot to diff against, drizzle-kit assumes the whole schema is new.

Where Drizzle keeps migration state, on disk and in the database
LocationWhat it holdsWho writes it
migrations/*.sqlThe statements applied to the databasedrizzle-kit generate
migrations/meta/*_snapshot.jsonSerialized schema after each migration, used as the diff basedrizzle-kit generate
migrations/meta/_journal.jsonOrdered index with a timestamp per migrationdrizzle-kit generate
__drizzle_migrations tableHash and timestamp of every migration already applieddrizzle-kit migrate

On the database side, the first migrate run creates a bookkeeping table called __drizzle_migrations. On PostgreSQL it is created inside a separate drizzle schema; on SQLite and Turso it is a plain table in the main database.

Each row records the hash of a migration file and the journal timestamp it was created with. When you run migrate, Drizzle reads the newest timestamp in that table and applies every journal entry that is newer than it. Anything older is treated as already done.

⚠ The timestamp comparison has a sharp edge

If a long-lived branch generates a migration on Monday, and a migration generated on Friday reaches production first, merging the Monday file afterwards does nothing: its journal timestamp is behind what the database already recorded, so migrate skips it silently. Regenerate after merging instead of merging the SQL files as they are.

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.

Running Migrations From Code

The CLI is the fastest path, but it needs drizzle-kit, your config file, and your schema source present at the moment you run it. That is fine in CI and awkward inside a slim production container where devDependencies have been pruned.

The alternative is a small script built on the migrator that ships inside drizzle-orm itself. It needs only the generated SQL files and a database connection, and it gives you a real exit code to branch on.

typescript — scripts/migrate.ts
import { config } from 'dotenv';
import { createClient } from '@libsql/client';
import { drizzle } from 'drizzle-orm/libsql';
import { migrate } from 'drizzle-orm/libsql/migrator';

config({ path: '.env.local' });

const client = createClient({
  url: process.env.TURSO_DATABASE_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN,
});

const db = drizzle(client);

try {
  await migrate(db, { migrationsFolder: './migrations' });
  console.log('Migrations applied');
} catch (err) {
  console.error('Migration failed:', err);
  process.exit(1);
} finally {
  client.close();
}

Run it with npx tsx scripts/migrate.ts. Each driver exports its own migrator, so only the import path changes: drizzle-orm/node-postgres/migrator, drizzle-orm/postgres-js/migrator, drizzle-orm/mysql2/migrator, drizzle-orm/better-sqlite3/migrator. The migrate call itself is identical in all of them.

The explicit process.exit(1) matters more than it looks. A migration script that logs an error and then exits with status 0 lets a broken deployment sail on to the next pipeline step, and the failure surfaces later as a runtime query error instead of a red build.

Note the dotenv call again: this script runs outside Next.js, so nothing loads .env.local for you. If the loading order of the various .env files is still fuzzy, our guide to Next.js environment variables covers which file wins in which context.

🚫 Danger

Do not call migrate() during application startup in a serverless or edge runtime. Every cold start would try to take a migration lock, concurrent instances would race each other, and a request-level CPU or wall-clock budget can kill the process halfway through a schema change. Migrations belong in a deploy step that runs exactly once.

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

Common drizzle-kit Errors and What They Mean

These are the failures that actually fill issue trackers, along with the reason each one happens. Most of them are the same misunderstanding in different clothes: drizzle-kit trusts the snapshot on disk, and the database trusts __drizzle_migrations, and the two can disagree.

The six drizzle-kit failures you are most likely to hit
What you seeCauseFix
Please specify 'dialect' param in configConfig written for an older drizzle-kit that used `driver`Replace `driver: 'pg'` with `dialect: 'postgresql'`
url: undefined, or a missing connection parameterdrizzle-kit never reads `.env.local` on its ownLoad it explicitly with `dotenv` inside `drizzle.config.ts`
No schema changes, nothing to migrateThe `schema` path in the config does not match the file you editedPoint `schema` at the real path, or use a glob like `./src/db/schema/*.ts`
table "users" already existsThe database was built with `push`, so nothing was recorded as appliedBaseline the database, or reset it if it is a dev database
A migration that already ran tries to run againAn applied `.sql` file was edited after the fact and its hash changedRestore the original file and add a new forward migration instead
Two migrations numbered 0002_Two branches generated migrations in parallelDelete both files, regenerate once from the merged schema

Switching from push to migrate on an existing database

This is the most common one-way door. You built the schema locally with push, the tables exist, and now you want a real migration history. The first migrate run tries to execute CREATE TABLE users and fails, because push never wrote a single row into __drizzle_migrations.

On a development database the answer is easy: drop everything and let the migration rebuild it from scratch. On a database that holds data you cannot lose, you baseline instead. Generate the initial migration so the SQL file and the snapshot both exist, then tell Drizzle that migration is already applied by inserting its bookkeeping row by hand.

sql — PostgreSQL: mark the first migration as already applied
CREATE SCHEMA IF NOT EXISTS drizzle;

CREATE TABLE IF NOT EXISTS drizzle."__drizzle_migrations" (
  id SERIAL PRIMARY KEY,
  hash text NOT NULL,
  created_at bigint
);

INSERT INTO drizzle."__drizzle_migrations" (hash, created_at)
VALUES ('<sha256 of 0000_init.sql>', 1717000000000);

The created_at value is the when field for that migration in meta/_journal.json. If computing the file hash by hand feels fragile, run the same migration once against a throwaway empty database, then copy the row Drizzle writes there into your real database. Same result, no guessing.

After baselining, verify by running migrate again. It should report nothing to apply. If it tries to recreate tables, the timestamp you inserted is older than the journal entry you meant to skip.

Rename prompts and accidental data loss

Drizzle cannot tell the difference between renaming a column and dropping one while adding another. Both produce an identical diff, so generate stops and asks you which one you meant.

bash
? Is email_address column in users table created or renamed from another column?
> + email_address              create column
  ~ email > email_address      rename column

Choose create column by mistake and the generated SQL drops email and adds an empty email_address. Every value in that column is gone the moment the migration runs, and the only way back is a backup.

push asks the same question and then applies your answer immediately, with no file to review in between. That is the single strongest argument for keeping it away from any database whose contents you care about.

💡 Tip

Read the generated .sql file in the pull request diff, every time. A migration containing DROP COLUMN, DROP TABLE, or ALTER COLUMN ... TYPE deserves a second reviewer and a backup taken minutes before deploy, not hours.

Migrations in CI/CD

Two jobs, two different responsibilities. On pull requests you verify that the committed migrations match the schema. On deploy you apply them, before the new code goes live.

The verification job is the one teams skip, and it is the one that prevents the classic outage: a schema change merged without its migration, so freshly deployed code queries a column that does not exist yet.

yaml — .github/workflows/db-verify.yml
name: Database

on:
  pull_request:
    paths:
      - "src/db/**"
      - "migrations/**"

jobs:
  verify:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci

      # Catches duplicate numbering and broken journal entries
      - run: npx drizzle-kit check

      # Fails if the schema changed but no migration was committed
      - name: Ensure migrations are up to date
        run: |
          npx drizzle-kit generate
          git diff --exit-code migrations/

Neither step needs database credentials. generate diffs against the committed snapshot and check only reads the journal, so this job runs happily on pull requests from forks where secrets are not available.

When git diff --exit-code fails, someone changed the schema and forgot the migration. The job log contains the diff itself, which is usually enough for the author to fix it without asking anyone what went wrong.

yaml — .github/workflows/deploy.yml
jobs:
  migrate:
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - name: Apply migrations
        env:
          TURSO_DATABASE_URL: ${{ secrets.TURSO_DATABASE_URL }}
          TURSO_AUTH_TOKEN: ${{ secrets.TURSO_AUTH_TOKEN }}
        run: npx drizzle-kit migrate

  deploy:
    needs: migrate
    runs-on: ubuntu-latest
    steps:
      - run: npm run deploy

The needs: migrate line is the entire point of splitting the jobs. If the migration fails, the deploy never starts and production keeps serving the previous build against the previous schema. Without it you get a half-applied change and an application that expects the finished one.

Scope the credentials to a GitHub environment so the production database token is not readable from every workflow in the repository, and so you can require a manual approval before it is used. Our post on GitHub Actions security walks through the wider hardening steps for workflows that hold secrets like these.

Drift is the other thing worth watching: staging and production quietly diverging because somebody applied a fix by hand at 2am. drizzle-kit introspect pointed at a live database writes out the schema it actually finds, which you can diff against your committed schema file to see exactly what moved.

Configuration drift usually arrives first. If staging and production point at databases you did not expect, compare the environment files before you go digging in the schema: our EnvDiff tool shows which keys differ, which are missing, and which have diverged in value.

Zero-Downtime Schema Changes

A migration and a deployment are two separate events, and in the gap between them the old application code is running against the new schema. Almost every unsafe schema change is really a problem with that gap.

One rule follows from it: a migration must be compatible with the code that is already deployed. When it cannot be, split it into steps that are.

Schema changes ranked by how much they care about deployment order
ChangeWhat goes wrongSafe sequence
Add a nullable columnNothingShip it in a single migration
Add a NOT NULL column with no defaultThe migration fails on any table that already has rowsAdd it nullable, backfill, then add the constraint
Drop a columnRunning instances still select it and start erroringRemove it from the code and deploy, drop it in the next release
Rename a columnOld and new code cannot both be correct at onceAdd the new column, write to both, backfill, drop the old one
Change a column typeTable rewrite holds a lock for the durationNew column, batched backfill, swap reads, drop the old
Add an index to a large PostgreSQL tableWrites block while the index buildsBuild it concurrently, outside the migration transaction

The pattern behind that right-hand column has a name: expand and contract. You expand the schema so both the old and the new code work against it, ship the code, then contract by removing whatever is no longer used.

A column rename becomes three releases:

  1. Expand: add email_address as a nullable column, and deploy code that writes to both email and email_address while still reading from email
  2. Migrate: backfill email_address from email, then deploy code that reads from email_address
  3. Contract: stop writing to email, deploy, then generate a migration that drops it

That feels heavy for a rename, and it is. It is also the only version with no window where live requests fail, and once the habit exists each step costs a few minutes.

The NOT NULL case is worth showing as SQL, because the generated migration will not do this for you. Drizzle emits a single ALTER TABLE ... ADD COLUMN ... NOT NULL, which fails immediately on a table that already has rows. Split it into two files, one release apart:

sql — migrations/0004_add_user_role.sql
ALTER TABLE "users" ADD COLUMN "role" text;
UPDATE "users" SET "role" = 'member' WHERE "role" IS NULL;
sql — migrations/0005_user_role_not_null.sql (once the backfill is done)
ALTER TABLE "users" ALTER COLUMN "role" SET NOT NULL;

You write the first file yourself: generate an empty migration with drizzle-kit generate --custom and put the statements in it. The second one you can let drizzle-kit produce normally, by adding .notNull() to the column in your schema once the backfill has finished everywhere.

If the locking behaviour behind that table is unfamiliar territory, the fundamentals are worth a detour. Our SQL database interview questions covers transactions, isolation levels, and index behaviour in the depth this section assumes.

And when a schema change implies a long-running data change, that work does not belong in a migration at all. It belongs in a job that can be retried, resumed, and observed, which is the argument our comparison of Postgres-backed durable workflows and Temporal works through in detail.

⚠ Warning

CREATE INDEX CONCURRENTLY cannot run inside a transaction, and the migrator wraps each migration file in one. Build concurrent indexes as a separate manual step against the database, then add the index to your schema so future diffs know it already exists.

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.

Can I run Drizzle migrations from a Cloudflare Worker or a serverless function?

You can import the migrator there, but you should not. Serverless and edge runtimes have short execution budgets and no guarantee that one instance handles the call, so a migration can be cut off partway through and leave the schema in a state nobody planned for.

Run migrations from the deployment pipeline instead, as a step that completes before the new code goes live. drizzle-kit migrate in a CI job, or a tsx scripts/migrate.ts step, both work and both fail loudly enough to stop the deploy.

What is the __drizzle_migrations table and is it safe to delete?

It is how Drizzle knows what has already run. Each row holds the hash of a migration file and the timestamp from meta/_journal.json, and migrate applies only the journal entries newer than the newest row it finds.

Deleting it does not undo anything. Your tables stay exactly as they are, but Drizzle now believes nothing has ever been applied, so the next migrate run tries to replay every migration from the beginning and fails on the first CREATE TABLE. If you have already deleted it, recreate the rows using the baselining approach described above.

Why does drizzle-kit generate report no changes when I clearly changed the schema?

Work through these in order. The first one is the cause the overwhelming majority of the time:

  1. The schema path in drizzle.config.ts does not point at the file you edited
  2. The table is defined but never exported: drizzle-kit only sees exported symbols
  3. You split the schema across several files and pointed schema at just one of them. Use a glob such as ./src/db/schema/*.ts, or an array of paths
  4. The change is not one drizzle-kit can express, for example a TypeScript-level type annotation that does not alter the column definition
  5. A previous generate already captured the change, and the migration file is sitting uncommitted in your working tree
How do I write a migration by hand for a backfill or a data fix?

Use the --custom flag. It creates an empty, correctly numbered migration file and registers it in the journal, and you supply the SQL yourself:

bash
npx drizzle-kit generate --custom --name=backfill_user_roles

This is the right home for data changes that must run exactly once, in a known position relative to the schema changes around them. Keep them small, and make them idempotent where you can: a WHERE clause that makes a rerun harmless costs nothing to write and saves an afternoon when a deploy is retried.

For anything that touches millions of rows, write the migration so it only changes the schema and move the data work into a batched background job. A migration that runs for twenty minutes holds locks for twenty minutes.

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.

Everything else in this guide follows from two facts worth keeping in your head: generate diffs against the snapshot on disk, and migrate decides what to run from the __drizzle_migrations table. When the tool behaves in a way that seems impossible, one of those two is out of sync with what you assumed.

Add the pull request check that regenerates migrations and fails on a dirty diff, gate the deploy job behind the migration job, and split anything destructive into expand and contract steps. Those three habits cover the failures that are expensive to recover from.

Zeeshan Tofiq

Zeeshan Tofiq

Full Stack Developer

Full stack developer with over 6 years of experience building production applications. Writes practical guides on JavaScript, TypeScript, React, Node.js, and cloud infrastructure. Focused on helping developers solve real problems with clean, maintainable code.

Enjoyed this article?

Get practical dev guides, tool updates, and new articles delivered straight to your inbox. No spam, unsubscribe anytime.

Related Articles

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·15 min read
javascript

5 async/await Mistakes That Slow Your JavaScript Code

Sequential awaits, await in forEach, missing Promise.all: these 5 async/await mistakes silently slow your JavaScript. Here's how to spot and fix each one.

May 30, 2026·20 min read

On this page

  • push vs migrate: The Key Distinction
  • How Drizzle Tracks Applied Migrations
  • Setting Up drizzle-kit
  • Install and configure drizzle-kit
  • Define schema and generate migrations
  • Turso/libSQL-specific setup
  • Run migrations in production
  • Running Migrations From Code
  • Troubleshooting
  • Common drizzle-kit Errors and What They Mean
  • Switching from push to migrate
  • Rename prompts and data loss
  • Migrations in CI/CD
  • Zero-Downtime Schema Changes
  • drizzle-kit Commands Reference
  • Frequently Asked Questions