A Laravel migration is a PHP class describing one schema change, checked into version control alongside the code that depends on it — so every environment (a teammate's machine, staging, production) can reach an identical database structure by running the same ordered set of files, instead of relying on someone manually running SQL by hand.
1. Creating a migration
php artisan make:migration create_products_table
Laravel infers intent from the migration's name — create_products_table pre-fills a Schema::create stub for a new table; add_price_to_products_table pre-fills an Schema::table stub for altering an existing one. The generated file lands in database/migrations/ with a timestamp prefix, which is what determines run order.
public function up(): void
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->decimal('price', 10, 2);
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('products');
}
2. up() and down()
up() applies the change; down() must undo exactly what up() did, in reverse. This isn't a formality — down() is what actually runs when a migration is rolled back, and a down() that doesn't correctly reverse the corresponding up() leaves the schema in a state that no longer matches what any migration file describes.
3. Running migrations
php artisan migrate
This runs every migration that hasn't already been applied, in timestamp order, and records each one in the migrations table — which is how Laravel knows which migrations are "new" the next time the command runs, rather than re-applying everything from scratch.
4. Rolling back
php artisan migrate:rollback # undo the last batch
php artisan migrate:rollback --step=3 # undo the last 3 migrations
php artisan migrate:reset # undo everything
A "batch" is every migration that ran together in a single migrate call — rollback without --step undoes that whole batch at once, in reverse order, not just the single most recent file.
5. Fresh and refresh, for local development
php artisan migrate:fresh # drop every table, then re-run all migrations
php artisan migrate:refresh # roll back everything, then re-run all migrations
migrate:fresh is faster because it drops tables directly rather than running every migration's down() method — the practical difference is that fresh skips validating that each down() actually works. Neither command belongs anywhere near a production database; both destroy all existing data.
6. Checking migration status
php artisan migrate:status
Lists every migration file alongside whether it's been run — the fastest way to check, before deploying, whether a teammate's new migration has already been applied on a given environment or is still pending.