Migrations & Seeders

So far in this track you've built queries against tables that already exist. In a real project, the tables themselves have to be created somewhere — and hand-writing raw SQL files for that is fragile and hard to keep in sync across a team. Migrations solve this: they let you describe your database schema as version-controlled PHP code, so every developer (and every environment — local, staging, production) can run the exact same changes in the exact same order. Alongside migrations, factories and seeders let you generate realistic fake data and populate a fresh database in seconds, which is invaluable for development and automated testing.

What Are Migrations?

A migration is a PHP class that describes one incremental change to your database schema — creating a table, adding a column, adding an index, and so on. Each migration file is timestamped, so Laravel always knows the exact order changes should be applied in. Because migrations live in your codebase and get committed to Git like any other file, your schema evolves alongside your application code, fully version-controlled, instead of living only inside someone's database client.

Why not just edit the database directly? If you add a column by hand in phpMyAdmin, nobody else on the team gets that change automatically, and there's no record of when or why it happened. Migrations turn schema changes into something you can review, share, and reverse — exactly like application code.

Generating a Migration

Migrations are created with the make:migration Artisan command. Laravel is smart enough to pre-fill a sensible skeleton based on the name you choose — a name starting with create_ and ending in _table automatically generates a "create table" stub for you.

php artisan make:migration create_articles_table

This creates a new timestamped file inside database/migrations/, something like 2026_08_15_120000_create_articles_table.php. The timestamp prefix is exactly how Laravel knows to run this migration after any migration created earlier.

The Schema Builder: up() and down()

Every migration class has two methods. up() describes the change to apply — usually creating or altering a table using Laravel's schema builder, a fluent, database-agnostic API for describing columns without writing raw SQL. down() describes how to reverse that exact change, so the migration can be rolled back cleanly if needed.

<?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('articles', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('body');
            $table->boolean('is_published')->default(false);
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('articles');
    }
};

Notice down() simply drops the table — it undoes exactly what up() did. For migrations that only add a column to an existing table, down() would drop just that column instead, keeping the rest of the table intact.

Common Column Type Methods

The schema builder offers a method for nearly every column type your database supports. A few of the ones you'll reach for constantly:

MethodWhat it creates
$table->id()Auto-incrementing unsigned big-integer primary key column named id
$table->string('title')A VARCHAR column, 255 characters by default
$table->text('body')A TEXT column for longer content
$table->integer('views')A standard integer column
$table->boolean('is_published')A boolean/tinyint column
$table->foreignId('user_id')->constrained()An unsigned big-integer foreign key column, with a matching foreign key constraint auto-inferred from the column name (user_id assumes the users table)
$table->timestamps()Adds both created_at and updated_at columns, managed automatically by Eloquent
Note: foreignId()->constrained() is exactly how you'd link the Article and Comment models from Eloquent Relationships at the database level — the foreign key constraint enforces that a comment can't reference an article that doesn't exist.

Running & Rolling Back Migrations

Once a migration file exists, run it with php artisan migrate. Laravel executes every migration that hasn't run yet, in timestamp order.

php artisan migrate

If you need to undo the most recent batch of migrations — say, you spot a mistake right after running them — migrate:rollback reverses them by calling each one's down() method, in reverse order.

php artisan migrate:rollback

A few other variants are worth knowing: migrate:fresh drops every table and re-runs all migrations from scratch (handy in local development), and migrate:status shows you which migrations have run and which are still pending.

The Migrations Table

Laravel keeps track of exactly which migrations have already been executed using its own database table, migrations, which it creates automatically the first time you run php artisan migrate. Each row simply records a migration's filename and which "batch" it ran in. This is how Laravel knows to skip migrations that already ran, and how migrate:rollback knows which batch to reverse.

Factories: Generating Fake Model Data

A factory defines a blueprint for generating fake instances of a model — useful for local development (so you're not staring at an empty database) and for tests (so you can create realistic records without manually typing out every field). Generate one with:

php artisan make:factory ArticleFactory

Inside the generated factory, the definition() method returns an array of fake column values, generated using Laravel's bundled Faker-style helper, $this->faker:

<?php

namespace Database\Factories;

use App\Models\Article;
use Illuminate\Database\Eloquent\Factories\Factory;

class ArticleFactory extends Factory
{
    protected $model = Article::class;

    public function definition(): array
    {
        return [
            'title' => $this->faker->sentence(),
            'body' => $this->faker->paragraphs(3, true),
            'is_published' => $this->faker->boolean(80),
        ];
    }
}

With a factory defined, you can create records straight from Artisan Tinker or anywhere in your code: Article::factory()->count(10)->create(); inserts ten fake articles into the database in one line.

Seeders: Populating the Database

A seeder is a class whose job is to populate the database with data — often by calling a factory, sometimes by inserting specific, known records (like a default admin user, or a fixed list of categories). Generate one with:

php artisan make:seeder ArticleSeeder
<?php

namespace Database\Seeders;

use App\Models\Article;
use Illuminate\Database\Seeder;

class ArticleSeeder extends Seeder
{
    public function run(): void
    {
        Article::factory()->count(25)->create();
    }
}

Seeders are triggered from the main DatabaseSeeder class (found in database/seeders/DatabaseSeeder.php), which you call other seeders from using $this->call(ArticleSeeder::class);. Run every registered seeder with:

php artisan db:seed

Full Worked Example: Migration + Factory + Seeder Together

Putting the pieces together, here's the complete flow for standing up an Article model with real, believable test data from nothing:

// 1. Create the table
php artisan make:migration create_articles_table

// 2. Create the factory
php artisan make:factory ArticleFactory --model=Article

// 3. Create the seeder
php artisan make:seeder ArticleSeeder

// 4. Run the migration to build the table
php artisan migrate

// 5. Run the seeder (which uses the factory) to populate it
php artisan db:seed --class=ArticleSeeder

After these five commands, you have a fully migrated articles table containing 25 realistic, randomly-generated rows — ready to query with everything you learned in Eloquent ORM Basics, without typing a single INSERT statement by hand.

Where this leads: Migrations create the tables your models live in — but how does Laravel actually build a Article $article and hand it to your controller automatically? That's the service container, covered next in Service Container & Dependency Injection.