skip to content

In Laravel, what does php artisan make:migration create_listings_table generate, and how do its up() and down() methods relate to migrate and migrate:rollback?

level: juniorimportance: must knowfreq 72%

answer

  1. timestamped file in database/migrations
  2. return new class extends Migration
  3. Schema::create with id() and timestamps()
  4. migrations table stores name and batch
  5. rollback runs down() for the last batch

basics

~20 s

It writes a timestamped file in database/migrations that returns an anonymous class extending Migration, with up() creating the listings table and down() dropping it. migrate runs pending up() methods as one batch; migrate:rollback runs down() for the last batch.

solid answer

~30 s

`make:migration create_listings_table` guesses the table from the `create_*_table` pattern and writes `database/migrations/<timestamp>_create_listings_table.php`. The file `return`s an **anonymous class** extending `Illuminate\Database\Migrations\Migration`, so two migrations can never collide on a class name. Its `up()` calls `Schema::create('listings', ...)` with `$table->id()` and `$table->timestamps()`; its `down()` calls `Schema::dropIfExists('listings')`. A name like `add_price_to_listings_table` instead produces `Schema::table()` calls for an existing table. `php artisan migrate` runs every migration not yet recorded in the `migrations` table, in filename order, and records them with a new **batch** number. `migrate:rollback` runs `down()` for the migrations in the latest batch and deletes their records, so `down()` must exactly undo `up()`.

code

bash · 5 lines
bash
php artisan make:migration create_listings_table
php artisan make:migration add_price_to_listings_table
php artisan migrate
php artisan migrate:status
php artisan migrate:rollback

go deeper

for a junior

Recall the anonymous class returned by the file, Schema::create versus Schema::table, and that migrate runs up() while rollback runs down().

for a middle

Explain the migrations table and batch numbers, how make:migration picks a stub from the name, and why down() must mirror up().

for a senior

Enforce that shipped migrations are immutable and reversible, and keep application models out of them so old migrations keep working.

for a principal

Set the team's rules for migration granularity, review and reversibility across many contributors and environments.

## What the generator writes A **migration** is a PHP file that describes one change to the database schema. Laravel keeps them in `database/migrations`, and each filename starts with a timestamp so that files sort in the order they were created: ```bash php artisan make:migration create_listings_table # database/migrations/2026_09_29_101500_create_listings_table.php ``` The command inspects the name. A `create_*_table` name (or `--create=listings`) produces a **create** stub; a name ending in `_to_listings_table`, `_from_listings_table` or `_in_listings_table` (or `--table=listings`) produces an **update** stub that uses `Schema::table()`. The create stub looks like this: ```php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('listings', function (Blueprint $table) { $table->id(); $table->timestamps(); }); } public function down(): void { Schema::dropIfExists('listings'); } }; ``` ## Why an anonymous class The file **returns** an instance of a class with no name. Older Laravel migrations declared named classes such as `CreateListingsTable`, which meant two migrations with the same name in one project collided. Because the migrator simply `require`s the file and uses the object it returns, anonymous classes remove that problem. ## `Schema::create` versus `Schema::table` - **`Schema::create('listings', fn (Blueprint $table) => ...)`** issues `CREATE TABLE`. The closure receives a `Blueprint`, on which each method call declares a column or index: `$table->string('title')`, `$table->decimal('price', 10, 2)`. - **`Schema::table('listings', ...)`** alters an existing table: add columns, add or drop indexes, or modify columns with `change()`. - **`$table->id()`** adds an auto-incrementing unsigned big-integer primary key named `id`; **`$table->timestamps()`** adds nullable `created_at` and `updated_at`. ## How `migrate` and `rollback` use the two methods Laravel records every migration it has run in a table named `migrations`, with the file name and a **batch** number: 1. `php artisan migrate` compares the files on disk with the rows in `migrations`, runs `up()` on each pending file in filename order, and records them all with the next batch number. 2. `php artisan migrate:rollback` finds the **latest batch**, runs `down()` on each of its migrations in reverse order, and deletes their rows. 3. `php artisan migrate:status` lists each migration with whether it has run and in which batch. Because rollback trusts `down()`, it must be the exact inverse of `up()`: a migration that adds two columns and an index should drop all three. An empty `down()` makes a rollback report success while leaving the schema unchanged. ## Habits that keep migrations healthy - **Never edit a migration that has already run** on a shared or production database. Laravel sees it as done and will not run it again; write a new migration instead. - **One concern per migration** makes rollbacks predictable. - **Keep models out of migrations** where possible: a model's code changes over time, while a migration must keep working against the schema as it was when it ran. ## Summary | Piece | Role | |---|---| | `make:migration` | writes a timestamped file from a create or update stub | | anonymous class | avoids class-name collisions between migrations | | `up()` | applies the change, run by `migrate` | | `down()` | reverses it, run by `migrate:rollback` | | `migrations` table | records which files ran, and in which batch |

  • You fixed a typo in a migration that already ran in production. Why does migrate report nothing to do?
    The migrator only compares file names with the rows in the `migrations` table. The edited file's name is already recorded, so Laravel treats it as done and never reads its new contents. Revert the edit and add a new migration that makes the correction, so every environment converges on the same schema.
  • What does make:migration generate when the name matches neither a create nor a change pattern?
    A blank stub whose `up()` and `down()` are empty, because Laravel cannot guess a table. Pass `--create=listings` or `--table=listings` to choose the create or update stub explicitly, or fill the methods in yourself.

saying these in an interview costs you the question

  • Thinks migrate re-runs a migration after its file is edited
  • Leaves down() empty and expects rollback to undo the change
  • Believes migrate:rollback reverts every migration ever run
  • Says migrations must declare a uniquely named class
  • Uses Schema::create to add a column to an existing table