Reader Stacks

Laravel Migration Column Operations: Add, Modify, Rename, and Drop Columns

Every column operation beyond a fresh migration — adding, changing type, renaming, dropping, indexing — goes through doctrine/dbal for anything that modifies an existing column, not just creates one.

Beyond the initial create_table migration, real projects constantly need to add, modify, rename, or drop individual columns on an existing table — each of these is a distinct migration pattern worth having as a reference.

Adding a new column

php artisan make:migration add_phone_to_users_table --table=users
public function up(): void
{
    Schema::table('users', function (Blueprint $table) {
        $table->string('phone')->nullable()->after('email');
    });
}

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

Adding a column with a default value

$table->boolean('is_active')->default(true);
$table->integer('sort_order')->default(0);
$table->string('status')->default('pending');

Changing an existing column's data type

public function up(): void
{
    Schema::table('products', function (Blueprint $table) {
        $table->decimal('price', 10, 2)->change();
    });
}

Modifying an existing column's type or attributes requires calling ->change() at the end of the column definition — this specific operation depends on the doctrine/dbal package being installed (composer require doctrine/dbal), unlike simply adding a brand-new column, which doesn't need it.

Renaming a column

public function up(): void
{
    Schema::table('users', function (Blueprint $table) {
        $table->renameColumn('name', 'full_name');
    });
}

Renaming also depends on doctrine/dbal on older Laravel versions — as of Laravel 9+, native rename support was added for common database drivers, reducing the doctrine/dbal dependency for this specific operation, though it's still worth checking based on the exact Laravel and database driver version in use.

Dropping a column

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

// Dropping multiple columns at once
public function up(): void
{
    Schema::table('users', function (Blueprint $table) {
        $table->dropColumn(['phone', 'fax']);
    });
}

Setting a nullable default explicitly

$table->string('middle_name')->nullable()->default(null);

Adding or updating an index

public function up(): void
{
    Schema::table('orders', function (Blueprint $table) {
        $table->index('customer_id');
        $table->unique('order_number');
        $table->index(['status', 'created_at']); // composite index
    });
}
// Dropping an index
Schema::table('orders', function (Blueprint $table) {
    $table->dropIndex(['customer_id']);
});

Dropping a foreign key

Schema::table('posts', function (Blueprint $table) {
    $table->dropForeign(['user_id']);
});

The array syntax here (['user_id']) tells Laravel to derive the conventional foreign key constraint name from the column itself — the constraint can also be dropped by its explicit name string if it was originally created with a custom name.

Adding an enum column, and updating its allowed values later

$table->enum('status', ['pending', 'active', 'suspended']);
// Changing an enum's allowed values requires raw SQL on most drivers
DB::statement("ALTER TABLE orders MODIFY COLUMN status ENUM('pending', 'active', 'suspended', 'cancelled')");

Laravel's schema builder doesn't have a first-class method for altering an existing enum's list of allowed values — a raw SQL statement, specific to the database driver in use, is typically needed for this particular change.

Always writing a working down() method

Every migration shown above should have a corresponding down() method that reverses it exactly — a dropped column's down() should re-add it, a renamed column's should rename it back — since skipping this makes php artisan migrate:rollback fail or leave the schema in an inconsistent state.