Skip to main content
Drizzle’s migration API allows you to programmatically apply schema changes to your database. Migrations are generated using drizzle-kit and applied at runtime using the migrate() function.

Import

import { migrate } from 'drizzle-orm/node-postgres/migrator';
// Or other PostgreSQL drivers:
// import { migrate } from 'drizzle-orm/postgres-js/migrator';
// import { migrate } from 'drizzle-orm/neon-http/migrator';

migrate()

Apply pending migrations to your database:
db
Database
required
The database instance (with schema type)
config
MigrationConfig
required
Migration configuration object

MigrationConfig

migrationsFolder
string
required
Path to the folder containing migration files (relative or absolute)
migrationsTable
string
Custom name for the migrations tracking table (default: __drizzle_migrations)
migrationsSchema
string
Schema name for the migrations table (PostgreSQL only)

Basic Usage

PostgreSQL

import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';
import * as schema from './schema';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

const db = drizzle(pool, { schema });

// Run migrations
await migrate(db, {
  migrationsFolder: './drizzle',
});

await pool.end();

MySQL

import { drizzle } from 'drizzle-orm/mysql2';
import { migrate } from 'drizzle-orm/mysql2/migrator';
import mysql from 'mysql2/promise';
import * as schema from './schema';

const connection = await mysql.createConnection({
  host: 'localhost',
  user: 'root',
  database: 'mydb',
});

const db = drizzle(connection, { schema });

// Run migrations
await migrate(db, {
  migrationsFolder: './drizzle',
});

await connection.end();

SQLite

import { drizzle } from 'drizzle-orm/better-sqlite3';
import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
import Database from 'better-sqlite3';
import * as schema from './schema';

const sqlite = new Database('sqlite.db');
const db = drizzle(sqlite, { schema });

// Run migrations
migrate(db, {
  migrationsFolder: './drizzle',
});

sqlite.close();
SQLite migrations in Drizzle are synchronous, unlike PostgreSQL and MySQL which are asynchronous.

Migration Files Structure

Migrations are stored in the folder specified by migrationsFolder. Drizzle Kit generates:
drizzle/
├── meta/
│   └── _journal.json
├── 0000_initial.sql
├── 0001_add_users.sql
└── 0002_add_posts.sql

_journal.json

Tracks migration metadata:
{
  "version": "5",
  "dialect": "pg",
  "entries": [
    {
      "idx": 0,
      "version": "5",
      "when": 1699564800000,
      "tag": "0000_initial",
      "breakpoints": true
    },
    {
      "idx": 1,
      "version": "5",
      "when": 1699651200000,
      "tag": "0001_add_users",
      "breakpoints": true
    }
  ]
}

Migration SQL Files

Contain SQL statements to modify the schema:
-- 0000_initial.sql
CREATE TABLE IF NOT EXISTS "users" (
	"id" serial PRIMARY KEY NOT NULL,
	"name" text NOT NULL,
	"email" text NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "email_idx" ON "users" ("email");
The --> statement-breakpoint comment separates individual statements for proper execution.

Custom Migrations Table

Custom Table Name

await migrate(db, {
  migrationsFolder: './drizzle',
  migrationsTable: 'my_migrations',
});
This creates a table named my_migrations instead of the default __drizzle_migrations.

Custom Schema (PostgreSQL)

await migrate(db, {
  migrationsFolder: './drizzle',
  migrationsTable: 'migrations',
  migrationsSchema: 'drizzle',
});
This creates the migrations table in the drizzle schema.

Migration Tracking

Drizzle automatically creates a migrations table to track which migrations have been applied:
-- PostgreSQL example
CREATE TABLE IF NOT EXISTS "__drizzle_migrations" (
  "id" serial PRIMARY KEY,
  "hash" text NOT NULL,
  "created_at" bigint
);
Each migration is recorded with:
  • id: Sequential identifier
  • hash: SHA-256 hash of the migration SQL
  • created_at: Unix timestamp in milliseconds

Application Startup Pattern

Common pattern for running migrations on application startup:

Express.js Example

import express from 'express';
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';
import * as schema from './schema';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

const db = drizzle(pool, { schema });

const app = express();

// Run migrations before starting server
async function startServer() {
  console.log('Running migrations...');
  await migrate(db, { migrationsFolder: './drizzle' });
  console.log('Migrations complete');
  
  app.listen(3000, () => {
    console.log('Server started on port 3000');
  });
}

startServer().catch(console.error);

NestJS Example

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';
import * as schema from './schema';

async function bootstrap() {
  // Run migrations
  const pool = new Pool({
    connectionString: process.env.DATABASE_URL,
  });
  
  const db = drizzle(pool, { schema });
  await migrate(db, { migrationsFolder: './drizzle' });
  await pool.end();
  
  // Start application
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}

bootstrap();

Separate Migration Script

Create a dedicated script for running migrations:

migrate.ts

import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';
import * as schema from './schema';

const runMigrations = async () => {
  const pool = new Pool({
    connectionString: process.env.DATABASE_URL,
  });

  const db = drizzle(pool, { schema });

  console.log('Running migrations...');

  try {
    await migrate(db, { migrationsFolder: './drizzle' });
    console.log('Migrations completed successfully');
  } catch (error) {
    console.error('Migration failed:', error);
    process.exit(1);
  } finally {
    await pool.end();
  }
};

runMigrations();

package.json

{
  "scripts": {
    "migrate": "tsx migrate.ts"
  }
}
Run migrations:
npm run migrate

Environment-Specific Migrations

Different migration configs for different environments:
import { drizzle } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/node-postgres/migrator';
import { Pool } from 'pg';
import * as schema from './schema';

const env = process.env.NODE_ENV || 'development';

const configs = {
  development: {
    connectionString: process.env.DEV_DATABASE_URL,
    migrationsFolder: './drizzle',
  },
  staging: {
    connectionString: process.env.STAGING_DATABASE_URL,
    migrationsFolder: './drizzle',
  },
  production: {
    connectionString: process.env.PROD_DATABASE_URL,
    migrationsFolder: './drizzle',
  },
};

const config = configs[env];

const pool = new Pool({
  connectionString: config.connectionString,
});

const db = drizzle(pool, { schema });

await migrate(db, {
  migrationsFolder: config.migrationsFolder,
});

await pool.end();

Error Handling

Graceful Error Handling

try {
  await migrate(db, { migrationsFolder: './drizzle' });
  console.log('Migrations applied successfully');
} catch (error) {
  if (error instanceof Error) {
    console.error('Migration error:', error.message);
    
    // Check for specific errors
    if (error.message.includes('_journal.json')) {
      console.error('Journal file not found. Run drizzle-kit generate first.');
    }
  }
  
  process.exit(1);
}

Validation Before Migration

import { existsSync } from 'fs';
import { join } from 'path';

const migrationsFolder = './drizzle';
const journalPath = join(migrationsFolder, 'meta', '_journal.json');

if (!existsSync(journalPath)) {
  console.error('No migrations found. Generate migrations with: drizzle-kit generate');
  process.exit(1);
}

await migrate(db, { migrationsFolder });

Best Practices

1. Run Migrations Before App Starts

Ensure database schema is up-to-date before accepting requests:
await migrate(db, { migrationsFolder: './drizzle' });
app.listen(3000);

2. Use Version Control

Commit migration files to version control:
# .gitignore - DO commit these
# drizzle/
# drizzle/meta/

3. Test Migrations

Test migrations in development/staging before production:
# Development
NODE_ENV=development npm run migrate

# Staging
NODE_ENV=staging npm run migrate

# Production
NODE_ENV=production npm run migrate

4. Backup Before Migration

Always backup production databases before applying migrations:
if (process.env.NODE_ENV === 'production') {
  console.log('Creating backup...');
  // Run backup command
  await createBackup();
}

await migrate(db, { migrationsFolder: './drizzle' });

5. Use Transactions

Migrations run within transactions automatically (where supported), ensuring atomicity.

Generating Migrations

Migrations are generated using Drizzle Kit:
# Generate migrations based on schema changes
npx drizzle-kit generate

# Specify config file
npx drizzle-kit generate --config=drizzle.config.ts

drizzle.config.ts

import type { Config } from 'drizzle-kit';

export default {
  schema: './src/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: {
    connectionString: process.env.DATABASE_URL!,
  },
} satisfies Config;

Migration Workflow

  1. Modify schema: Update your schema definitions
  2. Generate migration: Run drizzle-kit generate
  3. Review SQL: Check generated SQL files
  4. Apply migration: Run migrate() function
  5. Verify: Test that changes work as expected

Common Issues

Migration File Not Found

// Error: Can't find meta/_journal.json file
Solution: Run drizzle-kit generate to create migrations first.

Permission Denied

// Error: permission denied to create table
Solution: Ensure database user has sufficient privileges:
GRANT ALL PRIVILEGES ON DATABASE mydb TO myuser;

Concurrent Migrations

Multiple instances running migrations simultaneously can cause conflicts. Solution: Use migration locks or ensure only one instance runs migrations.

Build docs developers (and LLMs) love