Skip to main content

Command

php artisan make:migration {name} --module={module}
Create a new migration file in your module’s database/migrations directory.

Parameters

name
string
required
The name of the migrationConventions:
  • Use snake_case
  • Start with an action verb (create, add, update, drop)
  • Include the table name
Examples:
  • create_posts_table
  • add_status_to_posts_table
  • create_post_tag_pivot_table

Options

--module
string
required
The name of the module where the migration should be createdExample: --module=blog
--create
string
The table to be created
php artisan make:migration create_posts_table --create=posts --module=blog
--table
string
The table to be modified
php artisan make:migration add_status_to_posts --table=posts --module=blog
--path
string
The location where the migration file should be created (relative to application base path)
--realpath
boolean
Indicate that the provided path is a pre-resolved absolute path
--fullpath
boolean
Output the full path of the created migration

Examples

Create Table Migration

php artisan make:migration create_posts_table --create=posts --module=blog
Generates a migration file with the create table scaffold:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('posts', function (Blueprint $table) {
            $table->id();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};
Location: app-modules/blog/database/migrations/2026_02_28_120000_create_posts_table.php
The filename includes a timestamp prefix to ensure migrations run in the correct order.

Modify Table Migration

php artisan make:migration add_published_at_to_posts_table --table=posts --module=blog
Generates a migration to modify an existing table:
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::table('posts', function (Blueprint $table) {
            //
        });
    }

    public function down(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            //
        });
    }
};
Add your column definitions:
public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->timestamp('published_at')->nullable()->after('created_at');
    });
}

public function down(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->dropColumn('published_at');
    });
}

Pivot Table Migration

php artisan make:migration create_post_tag_table --create=post_tag --module=blog
Create a pivot table for many-to-many relationships:
public function up(): void
{
    Schema::create('post_tag', function (Blueprint $table) {
        $table->id();
        $table->foreignId('post_id')->constrained()->cascadeOnDelete();
        $table->foreignId('tag_id')->constrained()->cascadeOnDelete();
        $table->timestamps();
        
        $table->unique(['post_id', 'tag_id']);
    });
}

Basic Migration

php artisan make:migration update_posts_table_add_indexes --module=blog
Creates a blank migration for custom modifications:
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::table('posts', function (Blueprint $table) {
            $table->index('published_at');
            $table->index(['author_id', 'published_at']);
        });
    }

    public function down(): void
    {
        Schema::table('posts', function (Blueprint $table) {
            $table->dropIndex(['author_id', 'published_at']);
            $table->dropIndex(['published_at']);
        });
    }
};

Module-Specific Behavior

Migration Path Resolution

The --module option changes where migrations are stored:
MakeMigration.php:13-30
protected function getMigrationPath()
{
    $path = parent::getMigrationPath();
    
    if ($module = $this->module()) {
        $app_directory = $this->laravel->databasePath('migrations');
        $module_directory = $module->path('database/migrations');
        
        $path = str_replace($app_directory, $module_directory, $path);
        
        $filesystem = $this->getLaravel()->make(Filesystem::class);
        if (! $filesystem->isDirectory($module_directory)) {
            $filesystem->makeDirectory($module_directory, 0755, true);
        }
    }
    
    return $path;
}
This means:
php artisan make:migration create_posts_table
# Creates: database/migrations/2026_02_28_120000_create_posts_table.php

Automatic Directory Creation

The command automatically creates the database/migrations directory in your module if it doesn’t exist:
if (! $filesystem->isDirectory($module_directory)) {
    $filesystem->makeDirectory($module_directory, 0755, true);
}
You don’t need to manually create the migrations directory before running the command.

Running Migrations

Run All Migrations

Laravel automatically discovers and runs migrations from all modules:
php artisan migrate

Run Module Migrations Only

To run migrations from a specific path:
php artisan migrate --path=app-modules/blog/database/migrations

Rollback Migrations

# Rollback the last batch
php artisan migrate:rollback

# Rollback all migrations
php artisan migrate:reset

# Rollback and re-run all migrations
php artisan migrate:refresh

Check Migration Status

php artisan migrate:status

Common Migration Patterns

Foreign Key Constraints

public function up(): void
{
    Schema::create('comments', function (Blueprint $table) {
        $table->id();
        $table->foreignId('post_id')
              ->constrained()
              ->cascadeOnDelete();
        $table->text('content');
        $table->timestamps();
    });
}

Enum Columns

public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->enum('status', ['draft', 'published', 'archived'])
              ->default('draft');
    });
}

JSON Columns

public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->json('metadata')->nullable();
    });
}

Full-Text Indexes

public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->fullText(['title', 'content']);
    });
}

Soft Deletes

public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->softDeletes();
    });
}

public function down(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->dropSoftDeletes();
    });
}

Best Practices

Always implement the down() method to reverse the up() method:
public function up(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->string('slug')->unique();
    });
}

public function down(): void
{
    Schema::table('posts', function (Blueprint $table) {
        $table->dropColumn('slug');
    });
}
Migration names should clearly describe what they do:
  • add_published_at_to_posts_table
  • create_post_tag_pivot_table
  • update_posts
  • fix_database
Keep migrations focused on a single change for easier rollback:
# Good: Separate migrations
php artisan make:migration add_slug_to_posts_table --module=blog
php artisan make:migration add_published_at_to_posts_table --module=blog

# Avoid: One migration doing multiple things
php artisan make:migration update_posts_table --module=blog
Always add foreign key constraints for relationships:
$table->foreignId('user_id')
      ->constrained()
      ->cascadeOnDelete();
Index columns you’ll frequently query:
$table->index('published_at');
$table->index(['author_id', 'status']);

Migration Order

Migrations run in chronological order based on their timestamp prefix. If you need to ensure a specific order:
  1. Create migrations in the order you want them to run
  2. Or manually adjust the timestamp in the filename (not recommended)
  3. Or use dependencies in your migrations
Never modify a migration that has already been run in production. Create a new migration instead.

See Also

Build docs developers (and LLMs) love