Claude Code Plugins

Community-maintained marketplace

Feedback

Create Laravel database migrations following conventions for schema changes, indexes, and foreign keys. Use when creating migrations, modifying database schema, adding tables, or when user mentions migrations, database schema, schema changes, or database structure.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name creating-migrations
description Create Laravel database migrations following conventions for schema changes, indexes, and foreign keys. Use when creating migrations, modifying database schema, adding tables, or when user mentions migrations, database schema, schema changes, or database structure.

Create Laravel Database Migrations

Create database migrations that version schema changes with proper indexes and foreign keys. Migrations run sequentially to evolve the database structure, using timestamped filenames for ordering and descriptive names for clarity.

File Structure

Migrations are stored in database/migrations:

database/migrations/{timestamp}_{description}.php

Examples:

  • 2024_01_01_000000_create_users_table.php
  • 2024_01_02_000000_add_avatar_to_users_table.php
  • 2024_01_03_000000_create_orders_table.php

Core Conventions

1. Migration Structure

<?php

declare(strict_types=1);

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('table_name', function (Blueprint $table): void {
            $table->id();
            $table->string('name');
            $table->timestamps();
        });
    }

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

Key Requirements:

  • Always include declare(strict_types=1);
  • Use anonymous class extending Migration
  • Type hint closure parameter as Blueprint
  • Type hint closure return as void
  • Implement both up() and down() methods
  • Use dropIfExists() in down() method

2. Column Types

$table->id();                           // Auto-incrementing primary key
$table->string('email');                // VARCHAR(255)
$table->string('name', 100);            // VARCHAR(100)
$table->text('description');            // TEXT
$table->integer('count');               // INTEGER
$table->boolean('is_active');           // BOOLEAN
$table->timestamp('published_at');      // TIMESTAMP
$table->timestamps();                   // created_at, updated_at
$table->softDeletes();                  // deleted_at

3. Indexes and Constraints

$table->string('email')->unique();
$table->index('email');
$table->index(['user_id', 'created_at']);

$table->foreignId('user_id')
    ->constrained()
    ->cascadeOnDelete();

Examples

Example 1: Create Users Table

<?php

declare(strict_types=1);

use App\Support\Localisation\Facades\Localisation;
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('users', function (Blueprint $table): void {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->string('language')->default(Localisation::getDefaultLocale());
            $table->string('password');
            $table->text('two_factor_secret')->nullable();
            $table->text('two_factor_recovery_codes')->nullable();
            $table->rememberToken();
            $table->string('avatar_path', 2048)->nullable();
            $table->timestamp('email_verified_at')->nullable();
            $table->timestamp('two_factor_confirmed_at')->nullable();
            $table->timestamps();
        });

        Schema::create('password_reset_tokens', function (Blueprint $table): void {
            $table->string('email')->primary();
            $table->string('token');
            $table->timestamp('created_at')->nullable();
        });

        Schema::create('sessions', function (Blueprint $table): void {
            $table->string('id')->primary();
            $table->foreignId('user_id')->nullable()->index();
            $table->string('ip_address', 45)->nullable();
            $table->text('user_agent')->nullable();
            $table->longText('payload');
            $table->integer('last_activity')->index();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('users');
        Schema::dropIfExists('password_reset_tokens');
        Schema::dropIfExists('sessions');
    }
};

Example 2: Create Table with Foreign Keys

<?php

declare(strict_types=1);

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('orders', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('user_id')
                ->constrained()
                ->cascadeOnDelete();
            $table->string('status')->default('pending');
            $table->integer('total');
            $table->text('notes')->nullable();
            $table->timestamp('completed_at')->nullable();
            $table->timestamp('cancelled_at')->nullable();
            $table->timestamps();

            $table->index(['user_id', 'status']);
            $table->index('created_at');
        });
    }

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

Example 3: Add Column to Existing Table

<?php

declare(strict_types=1);

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('users', function (Blueprint $table): void {
            $table->string('phone')->nullable()->after('email');
            $table->boolean('is_active')->default(true)->after('email_verified_at');
        });
    }

    public function down(): void
    {
        Schema::table('users', function (Blueprint $table): void {
            $table->dropColumn(['phone', 'is_active']);
        });
    }
};

Example 4: Create Pivot Table

<?php

declare(strict_types=1);

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('order_product', function (Blueprint $table): void {
            $table->foreignId('order_id')
                ->constrained()
                ->cascadeOnDelete();
            $table->foreignId('product_id')
                ->constrained()
                ->cascadeOnDelete();
            $table->integer('quantity')->default(1);
            $table->integer('price');
            $table->timestamps();

            $table->primary(['order_id', 'product_id']);
        });
    }

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

Example 5: Modify Column

<?php

declare(strict_types=1);

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('users', function (Blueprint $table): void {
            $table->string('email', 320)->change();
            $table->text('bio')->nullable()->change();
        });
    }

    public function down(): void
    {
        Schema::table('users', function (Blueprint $table): void {
            $table->string('email', 255)->change();
            $table->string('bio', 500)->nullable()->change();
        });
    }
};

Foreign Key Conventions

// Basic foreign key
$table->foreignId('user_id')->constrained();

// With cascade delete
$table->foreignId('user_id')
    ->constrained()
    ->cascadeOnDelete();

// With cascade update
$table->foreignId('user_id')
    ->constrained()
    ->cascadeOnUpdate();

// With set null
$table->foreignId('user_id')
    ->nullable()
    ->constrained()
    ->nullOnDelete();

// Reference specific table
$table->foreignId('owner_id')
    ->constrained('users')
    ->cascadeOnDelete();

Indexes

// Single column index
$table->index('email');

// Compound index
$table->index(['user_id', 'created_at']);

// Unique index
$table->unique('email');

// Named index
$table->index('email', 'users_email_index');

Anti-Patterns

❌ Don't Do This

// Don't skip type hints on closure
Schema::create('users', function ($table) { // ❌ Type hint Blueprint

// Don't skip return type on closure
Schema::create('users', function (Blueprint $table) { // ❌ Add : void

// Don't use drop() in down()
public function down(): void
{
    Schema::drop('users'); // ❌ Use dropIfExists()
}

// Don't forget indexes
$table->foreignId('user_id')->constrained(); // ❌ Add cascadeOnDelete()

// Don't use class keyword for migration
final class CreateUsersTable extends Migration // ❌ Use anonymous class
{
}

// Don't forget timestamps
$table->id();
$table->string('name');
// ❌ Missing $table->timestamps()

✅ Do This Instead

// Type hint closure parameter
Schema::create('users', function (Blueprint $table): void {

// Add return type on closure
Schema::create('users', function (Blueprint $table): void {

// Use dropIfExists()
public function down(): void
{
    Schema::dropIfExists('users');
}

// Add appropriate cascades
$table->foreignId('user_id')
    ->constrained()
    ->cascadeOnDelete();

// Use anonymous class
return new class extends Migration
{
}

// Always add timestamps
$table->id();
$table->string('name');
$table->timestamps();

Running Migrations

# Run migrations
php artisan migrate

# Rollback last batch
php artisan migrate:rollback

# Rollback all migrations
php artisan migrate:reset

# Fresh database (drop all tables)
php artisan migrate:fresh

# Fresh with seeders
php artisan migrate:fresh --seed

Quality Standards

  • All migrations must pass PHPStan level 8
  • Code formatted with Pint
  • Proper foreign key constraints
  • Appropriate indexes for query performance
  • Reversible migrations (proper down() method)
  • Named consistently with timestamp prefix