Database Migrations Done Right

matt
Matthew Gros · Nov 15, 2025

TLDR

Small incremental changes, always reversible, test on staging first, never edit deployed migrations.

Database Migrations Done Right

Schema Changes Should Be Boring

No surprises. No downtime. No data loss.

Basic Migrations

// Create table
Schema::create('posts', function (Blueprint $table) {
    $table->id();
    $table->foreignId('user_id')->constrained();
    $table->string('title');
    $table->text('content');
    $table->timestamps();
});

// Modify table
Schema::table('posts', function (Blueprint $table) {
    $table->string('slug')->after('title');
    $table->index('slug');
});

Always Reversible

public function up(): void
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('phone')->nullable();
    });
}

public function down(): void
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn('phone');
    });
}

Safe Column Changes

Adding columns is safe. Modifying requires care:

// Safe - new nullable column
$table->string('nickname')->nullable();

// Careful - changing type
$table->string('age')->change();  // Was integer, might fail

// Careful - adding NOT NULL
$table->string('required_field');  // Fails if table has data

Data Migrations

Separate schema from data:

// Migration: add column
$table->string('status')->default('pending');

// Seeder or command: populate data
User::whereNull('status')->update(['status' => 'active']);

Golden Rules

  1. Never edit deployed migrations - Create new ones
  2. Test on staging first - Always
  3. Small incremental changes - Easier to debug
  4. Backup before migrating - In production

Zero-Downtime Pattern

For large tables:

// Step 1: Add nullable column
$table->string('new_field')->nullable();

// Deploy, run migration

// Step 2: Populate data (in chunks)
User::chunk(1000, fn($users) => /* update */);

// Step 3: Make non-nullable
$table->string('new_field')->nullable(false)->change();

About the Author

matt

I build and ship automation-driven products using Laravel and modern frontend stacks (Vue/React), with a focus on scalability, measurable outcomes, and tight user experience. I’m based in Toronto, have 13+ years in PHP, and I also hold a pilot’s license. I enjoy working on new tech projects and generally exploring new technology.