Migrations

Migrations provide version control for your database schema. Each migration is a PHP class that represents a set of database changes.

Please note that only MySQL/MariaDB based migrations are supported.

Creating Migrations

To create a new migration file, fire this command from console:

php console create:migration create_table_products

This will create the migration file prefixed with current datetime in database/migrations folder. The migration class contains methods up() and down().

The up() method contains definition for required schema changes. Any reverse operations should go inside down() folder.

Method Called when Purpose Common contents
up() When you apply or run a migration Build or evolve the schema—create tables, add columns/indexes/constraints, insert seed data that must exist, rename things, etc. DDL or data-manipulating statements written in the migration DSL
down() When you roll back or undo a migration Reverse whatever up() did so the database returns to its previous state The inverse DDL (drop tables, remove columns, delete seed rows, etc.)

In short: up() is the do part of a migration; down() is the undo.

Running Migrations

To run your migration files:

php console migrate:up

This command will run the up() method in all the migration scripts defined inside database/migrations folder.

To track the files that have been migrated, it will also create a migrations table in the database.

Rollback Migrations

To rollback or undo all your migrations:

php console migrate:down

This will run the down() method in all the migration scripts inside database/migrations folder.

To rollback a limited number of migrations, provide the steps flag. For example, this will rollback last two batches of migrations if present.

php console migrate:down --steps=2

To rollback all the migrations in one go, provide the --all flag:

php console migrate:down --all

Defining Migrations

All table operations take a closure that receives a Table object:

$this->create('users', function(Table $table) {
    // column definitions, indexes, foreign keys...
});

$this->alter('users')->add(function(Table $table) {
    // add columns, indexes, foreign keys...
});

Complete Example

public function up(): void
{
    $this->create('posts', function(Table $table) {
        $table->id();
        $table->varchar('title');
        $table->varchar('slug')->unique();
        $table->text('body')->nullable();
        $table->foreignKey('user_id')->references('id')->on('users');
        $table->timestamps();
    });
}

public function down(): void
{
    $this->drop('posts');
}

Create Table

public function up(): void
{
    $this->create('users', function(Table $table) {
        $table->id();
        $table->varchar('name');
        $table->varchar('email');
    });
}

public function down(): void
{
    $this->drop('users');
}

Rename Table

public function up(): void
{
    $this->rename('users', 'customers');
}

public function down(): void
{
    $this->rename('customers', 'users');
}

Truncate Table

public function up(): void
{
    $this->truncate('users');
}

Execute Raw SQL

You can execute raw SQL queries when needed:

public function up(): void
{
    $this->execute('ALTER TABLE users ADD COLUMN custom_field VARCHAR(255)');
}

Alter Table

You may alter an existing table definition as documented below.

Add New Columns

public function up(): void
{
    $this->alter('users')->add(function(Table $table) {
        $table->varchar('password');
        $table->timestamps();
    });
}

Modify Existing Columns

public function up(): void
{
    $this->alter('users')->modify(function(Table $table) {
        $table->varchar('name', 55);
    });
}
// Modify an enum column
public function up(): void
{
    $this->alter('users')->modify(function(Table $table) {
        $table->enum('status', ['active', 'inactive', 'banned']);
    });
}

Drop Existing Columns

public function up(): void
{
    // Drop single column
    $this->alter('users')->dropColumn('password');
}

public function down(): void
{
    // Drop multiple columns at once
    $this->alter('users')->dropColumn('password', 'email', 'phone');
}

Rename Column

public function up(): void
{
    $this->alter('users')->renameColumn('name', 'full_name');
}

public function down(): void
{
    $this->alter('users')->renameColumn('full_name', 'name');
}

Add/Drop Indexes

When altering a table, add or remove indexes directly:

// Add indexes
$this->alter('users')->add(function(Table $table) {
    $table->index('email');
    $table->unique('phone');
});

// Drop indexes by name
$this->alter('users')->dropIndex('email_index');
$this->alter('users')->dropUnique('phone_unique');

Add Foreign Keys to Existing Tables

// Add a foreign key to an existing table
public function up(): void
{
    $this->alter('products')->add(function(Table $table) {
        $table->foreignKey('category_id')->references('id')->on('categories');
    });
}

// Add a column and foreign key together
public function up(): void
{
    $this->alter('products')->add(function(Table $table) {
        $table->column('category_id')->type('bigint')->attribute('unsigned');
        $table->foreignKey('category_id')->references('id')->on('categories');
    });
}

Table Columns

All column methods return a Column object for chaining configuration.

Column Types

Type Method Notes
Auto PK id(string $name = 'id') BIGINT UNSIGNED AUTO_INCREMENT
Integer int($name, $len=11)
bigint($name)
smallint($name)
tinyint($name)
boolean($name, $default=false) TINYINT(1)
String varchar($name, $len=255)
char($name, $len=255)
text($name)
tinytext($name)
mediumtext($name)
longtext($name)
enum($name, $values)
json($name)
Date/Time date($name)
time($name)
datetime($name)
timestamp($name)
year($name)
Timestamps createdAt() Default CURRENT_TIMESTAMP
updatedAt() Nullable, ON UPDATE CURRENT_TIMESTAMP
deletedAt() Nullable
timestamps() Adds createdAt + updatedAt
Special ipAddress($name='ip_address') VARCHAR(45)
macAddress($name='mac_address') VARCHAR(17)
morphs($name) Adds {name}_id + {name}_type
Numeric decimal($name, $p=10, $s=2)

Column Configuration

Chain these after any column method:

$table->varchar('username', 50)->unique()->nullable();
$table->int('age')->default(0)->unsigned();
$table->decimal('balance', 12, 2)->attribute('UNSIGNED');
$table->datetime('created_at')->current();
Method Purpose
type(string) Override SQL type
length(int) Set length
default(string\|bool) Set default value
nullable() Allow NULL
attribute(string) Add SQL attribute (UNSIGNED, etc.)
unsigned() Shortcut for UNSIGNED
current() Shortcut for CURRENT_TIMESTAMP default
increments() AUTO_INCREMENT + PRIMARY KEY
primary() Mark as primary key
unique(?string $name) Add unique index
index(?string $name) Add regular index
fulltext(?string $name) Add fulltext index

Table Indexes

Supported Index Types

Index Naming

You can provide a custom name, or let the framework generate one based on the column name(s) and index type.

How defined Default name Example
Chained on column: ->unique() {column}_{type} $table->varchar('email')->unique()email_unique
Standalone single: ->unique('email') {column}_{type} $table->unique('email')email_unique
Standalone composite: ->unique(['a','b']) {col1}_{col2}_{type} $table->unique(['first','last'])first_last_unique
Composite too long (>60 chars) idx_{8charhash} $table->unique(['very_long_name', ...])idx_a1b2c3d4
Custom name provided Your name $table->unique('email', 'u_email')u_email

Tip: When dropping an index, pass the exact name shown in SHOW INDEXES or generated as above.


primary()

// single primary key
$table->primary('id');

// composite key
$table->primary(['user_id', 'post_id']);

dropPrimary()

Important Notes:

$table->dropPrimary();

unique()

Note: You should remove duplicate values from the columns before adding unique index otherwise it may result in "mysql error 1062".

$table->unique('email');
$table->unique(['first', 'last'], 'name_unique');

dropUnique()

$this->alter('users')->dropUnique('email_unique');

index()

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

dropIndex()

$this->alter('users')->dropIndex('user_status_idx');

fulltext()

$table->fulltext('body');
$table->fulltext(['title', 'body'], 'post_fulltext');

dropFulltext()

// Drop single fulltext index
$this->alter('posts')->dropFulltext('post_fulltext');

// Drop multiple fulltext indexes
$this->alter('posts')->dropFulltext('title_fulltext', 'body_fulltext');

spatial()

$table->spatial('location', 'loc_idx');

dropSpatial()

$this->alter('locations')->dropSpatial('loc_idx');

Table Configuration

You can configure table engine, charset, and collation:

public function up(): void
{
    $this->create('users', function(Table $table) {
        $table->id();
        $table->varchar('name');

        // Configure table settings
        $table->engine('InnoDB');  // Default: InnoDB
        $table->charset('utf8mb4'); // Default: utf8mb4
        $table->collation('utf8mb4_unicode_ci'); // Default: utf8mb4_unicode_ci
    });
}

Foreign Keys

Foreign keys enforce referential integrity between tables, ensuring that a column (or set of columns) in one table matches the primary key or unique key in another table. Foreign keys can be defined during table creation or added/removed during schema alteration. The following document details the support for working with foreign keys.

Defining Foreign Keys

To define a foreign key constraint, use the foreignKey() method. The constraint name is auto-generated as fk_{table}_{column} — always predictable. Use ->name() to override.

$table->foreignKey('author_id')
    ->references('id')
    ->on('users')
    ->cascadeOnDelete();
// Generates: CONSTRAINT `fk_posts_author_id` FOREIGN KEY (`author_id`) REFERENCES `users`(`id`) ON DELETE CASCADE ON UPDATE RESTRICT

// Custom constraint name:
$table->foreignKey('author_id')->name('my_fk_name')->cascadeOnDelete();

Foreign Key Actions

Available actions for ON UPDATE and ON DELETE:

Available Methods:

Note: Default behavior is RESTRICT for both ON UPDATE and ON DELETE.

Example:

$table->foreignKey('category_id')
    ->references('id')
    ->on('categories')
    ->nullOnDelete()      // Set to NULL when parent is deleted
    ->restrictOnUpdate(); // Prevent parent updates if children exist

Dropping Foreign Keys

Constraint names follow the pattern fk_{table}_{column}, so they are always predictable:

public function up(): void
{
    // Drop single foreign key
    $this->alter('posts')->dropForeign('fk_posts_author_id');

    // Drop multiple foreign keys
    $this->alter('posts')->dropForeign('fk_posts_author_id', 'fk_posts_category_id');
}

Note: If you used ->name() to set a custom constraint name, pass that name to dropForeign() instead.