Migrations handle nearly every schema change in a Laravel app, and while the individual operations are each simple, remembering the exact syntax for a less-common one (dropping a foreign key, changing a column's type) is where people usually reach for a reference.
Adding a column
Schema::table('users', function (Blueprint $table) {
$table->string('phone')->nullable()->after('email');
});
Adding a default value to a column
Schema::table('posts', function (Blueprint $table) {
$table->string('status')->default('draft');
});
Changing a column's data type
Schema::table('products', function (Blueprint $table) {
$table->decimal('price', 10, 2)->change();
});
Changing a column's type requires the doctrine/dbal package on Laravel versions before 11 — Laravel 11 and later dropped this requirement, running column-change migrations without it.
Renaming a column
Schema::table('users', function (Blueprint $table) {
$table->renameColumn('name', 'full_name');
});
Deleting a column
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('middle_name');
});
Setting a default null value explicitly
Schema::table('orders', function (Blueprint $table) {
$table->timestamp('shipped_at')->nullable()->default(null)->change();
});
Adding or updating an index
Schema::table('users', function (Blueprint $table) {
$table->index('email');
// or a composite index
$table->index(['last_name', 'first_name']);
});
Defining relations (foreign keys) in a migration
Schema::table('posts', function (Blueprint $table) {
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
});
foreignId()->constrained() assumes the related table is the plural of the column name minus _id (user_id → users) — pass a table name explicitly (constrained('authors')) when that convention doesn't hold.
Dropping a foreign key
Schema::table('posts', function (Blueprint $table) {
$table->dropForeign(['user_id']);
});
Creating or updating an enum column
$table->enum('status', ['draft', 'published', 'archived'])->default('draft');
// changing an existing enum's allowed values later
Schema::table('posts', function (Blueprint $table) {
$table->enum('status', ['draft', 'published', 'archived', 'scheduled'])->change();
});
Rolling back the most recent migration batch vs. a specific migration
php artisan migrate:rollback
// roll back just the last batch (default), or a specific number of batches
php artisan migrate:rollback --step=1
For rolling back a single specific migration rather than a whole batch, temporarily commenting out or removing the others from that batch, or writing a manual down-migration and running it directly, are the practical workarounds — Artisan's rollback command operates on batches, not individual files.
Running a raw SQL query inside a migration
public function up()
{
DB::statement('ALTER TABLE posts ADD FULLTEXT search_index (title, body)');
}
Dropping to raw SQL inside a migration is a reasonable escape hatch for database-specific features (like MySQL full-text indexes) the schema builder doesn't have a fluent method for.