Laravel Migrations

Laravel migrations are version control for your database. Each migration is a PHP class describing a schema change, so your whole team and every server can build the same tables with one command instead of running SQL by hand.

Creating a Migration

php artisan make:migration create_posts_table
INFO  Migration [database/migrations/2026_10_31_120000_create_posts_table.php] created successfully.

Defining Columns

The up method applies the change; down reverses it. The Schema builder has a method for every column type.

<?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('posts', function (Blueprint $table) {
            $table->id();
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('body');
            $table->enum('status', ['draft', 'published'])->default('draft');
            $table->timestamp('published_at')->nullable();
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};
posts: id, user_id (FK users.id), title, slug (unique), body, status, published_at, created_at, updated_at

Running and Rolling Back Laravel Migrations

php artisan migrate            # run new migrations
php artisan migrate:status     # see what ran
php artisan migrate:rollback   # undo the last batch
php artisan migrate:fresh --seed  # drop all tables, re-run, seed (dev only!)
INFO  Running migrations.
2026_10_31_120000_create_posts_table ........ 18ms DONE

Changing an Existing Table

Never edit a migration that already ran in production. Create a new one that alters the table.

php artisan make:migration add_views_to_posts_table

<?php
public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->unsignedInteger('views')->default(0)->after('body');
        $table->index('published_at');
    });
}

public function down(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->dropIndex(['published_at']);
        $table->dropColumn('views');
    });
}
INFO  Running migrations.
2026_10_31_130000_add_views_to_posts_table .... 9ms DONE

Seeders and Factories

Factories generate fake model data and seeders insert it. They are perfect for local development and tests.

<?php
// database/seeders/DatabaseSeeder.php
public function run(): void
{
    User::factory()
        ->count(5)
        ->has(Post::factory()->count(3))
        ->create();
}

// php artisan db:seed
INFO  Seeding database.
(5 users and 15 posts created)

Frequently asked questions

How do I add a column to an existing table in Laravel?

Create a new migration with make:migration add_column_to_table_table, use Schema::table() to add the column, then run php artisan migrate.

What is the difference between migrate:fresh and migrate:refresh?

migrate:fresh drops all tables and re-runs every migration. migrate:refresh rolls back each migration using down() then runs them again. Both destroy data.

What does constrained() do?

On a foreignId column it adds a foreign key to the conventional table, so user_id references id on users.