Skip to main content

Introduction

Migrations are version control for your database schema. They allow you to define and share your application’s database schema definition, making it easy to modify and share the database structure across your team.

Creating Migrations

Using the Migrator

Create a new migration file using the Migrator class:
use Lyger\Database\Migrator;

$migrator = new Migrator();
$migrator->make('create_users_table');

// Output: Created migration: 2026_03_08_143052_create_users_table.php
This creates a migration file in the database/migrations/ directory with a timestamp prefix.

Migration File Structure

A generated migration file looks like this:
database/migrations/2026_03_08_143052_create_users_table.php
<?php

declare(strict_types=1);

use Lyger\Database\Migration;

class CreateUsersTable extends Migration
{
    public function up(): void
    {
        $this->getSchema()->create('users', function ($table) {
            $table->id();
            // Add your columns here
            // $table->string('name');
            // $table->timestamps();
        });
    }

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

Migration Structure

Every migration has two methods:
up()
void
Runs when the migration is executed. Define table creation and modifications here.
down()
void
Runs when the migration is rolled back. Reverse the changes made in up().

Creating Tables

Basic Table Creation

public function up(): void
{
    $this->getSchema()->create('posts', function ($table) {
        $table->id();
        $table->string('title');
        $table->text('content');
        $table->string('status');
        $table->timestamps();
    });
}

Complete Example

public function up(): void
{
    $this->getSchema()->create('users', function ($table) {
        // Primary key
        $table->id();
        
        // String columns
        $table->string('name', 255);
        $table->string('email', 255)->unique();
        $table->string('password');
        
        // Text columns
        $table->text('bio')->nullable();
        
        // Integer columns
        $table->integer('age')->unsigned();
        $table->bigInteger('views')->default(0);
        
        // Boolean columns
        $table->boolean('active')->default(1);
        $table->boolean('email_verified')->default(0);
        
        // Date/Time columns
        $table->datetime('last_login')->nullable();
        $table->timestamp('email_verified_at')->nullable();
        
        // Timestamps (created_at, updated_at)
        $table->timestamps();
        
        // Soft deletes
        $table->softDeletes();
        
        // JSON columns
        $table->json('metadata')->nullable();
    });
}

Available Column Types

Numeric Types

$table->id(); // Auto-increment INTEGER PRIMARY KEY
$table->bigId(); // Alias for id()

$table->integer('votes');
$table->bigInteger('impressions');

$table->integer('count')->unsigned(); // Unsigned integer

String Types

$table->string('name'); // VARCHAR(255)
$table->string('code', 10); // VARCHAR(10)
$table->text('description'); // TEXT

Date and Time Types

$table->date('birth_date');
$table->datetime('published_at');
$table->timestamp('verified_at');

// Convenience methods
$table->timestamps(); // created_at, updated_at
$table->softDeletes(); // deleted_at

Special Types

$table->json('options'); // JSON data
$table->uuid(); // UUID string (36 chars)
$table->uuid('identifier'); // Custom UUID column

Column Modifiers

Chain modifiers to customize column behavior:
$table->string('email')->nullable(); // Allow NULL
$table->string('name')->default('Guest'); // Default value
$table->integer('price')->unsigned(); // Unsigned integer
$table->string('code')->unique(); // Add unique constraint
$table->string('title')->primary(); // Set as primary key

Nullable Columns

$table->string('middle_name')->nullable();
$table->text('bio')->nullable();
$table->datetime('deleted_at')->nullable();

Default Values

$table->string('status')->default('pending');
$table->boolean('active')->default(1);
$table->integer('views')->default(0);

Unsigned Integers

$table->integer('count')->unsigned();
$table->bigInteger('total')->unsigned();

Indexes

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

// Regular index
$table->string('status')->index();

// Composite index
$table->index(['user_id', 'post_id']);

Modifying Tables

Adding Columns

public function up(): void
{
    $this->getSchema()->table('users', function ($table) {
        $table->string('phone')->nullable();
        $table->text('address')->nullable();
    });
}

Dropping Tables

Drop Table

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

Drop If Exists

public function up(): void
{
    $this->getSchema()->dropIfExists('old_table');
}

Running Migrations

Run All Pending Migrations

use Lyger\Database\Migrator;

$migrator = new Migrator();
$migrator->run();

// Output:
// Running migration: 2026_03_08_143052_create_users_table
// Migrated: 2026_03_08_143052_create_users_table

Custom Migration Path

$migrator = new Migrator();
$migrator->path('/custom/path/to/migrations');
$migrator->run();

Rolling Back Migrations

Rollback Last Batch

$migrator = new Migrator();
$migrator->rollback();

// Output:
// Rolling back migration: 2026_03_08_143052_create_users_table
// Rolled back: 2026_03_08_143052_create_users_table

Reset All Migrations

$migrator = new Migrator();
$migrator->reset();
// Rolls back all migrations

Migration Status

Check which migrations have been run:
$migrator = new Migrator();
$migrator->status();

// Output:
// | Migration |
// |-----------|
// | 2026_03_08_143052_create_users_table | Ran |
// | 2026_03_08_143053_create_posts_table | Pending |

Complete Migration Examples

Users Table

<?php

declare(strict_types=1);

use Lyger\Database\Migration;

class CreateUsersTable extends Migration
{
    public function up(): void
    {
        $this->getSchema()->create('users', function ($table) {
            $table->id();
            $table->string('name');
            $table->string('email')->unique();
            $table->string('password');
            $table->boolean('email_verified')->default(0);
            $table->timestamp('email_verified_at')->nullable();
            $table->datetime('last_login')->nullable();
            $table->timestamps();
            $table->softDeletes();
        });
    }

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

Posts Table with Foreign Key

<?php

declare(strict_types=1);

use Lyger\Database\Migration;

class CreatePostsTable extends Migration
{
    public function up(): void
    {
        $this->getSchema()->create('posts', function ($table) {
            $table->id();
            $table->string('title');
            $table->string('slug')->unique();
            $table->text('content');
            $table->string('status')->default('draft');
            $table->integer('user_id')->unsigned();
            $table->integer('views')->unsigned()->default(0);
            $table->boolean('published')->default(0);
            $table->datetime('published_at')->nullable();
            $table->json('metadata')->nullable();
            $table->timestamps();
            $table->softDeletes();
            
            // Add index for better query performance
            $table->index(['user_id', 'status']);
        });
    }

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

Pivot Table for Many-to-Many

<?php

declare(strict_types=1);

use Lyger\Database\Migration;

class CreatePostTagTable extends Migration
{
    public function up(): void
    {
        $this->getSchema()->create('post_tag', function ($table) {
            $table->id();
            $table->integer('post_id')->unsigned();
            $table->integer('tag_id')->unsigned();
            $table->timestamps();
            
            // Composite unique constraint
            $table->index(['post_id', 'tag_id']);
        });
    }

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

Best Practices

1

Always Include Timestamps

Use $table->timestamps() to track creation and modification times:
$table->timestamps();
2

Use Descriptive Names

Name your migrations clearly:
  • create_users_table
  • add_status_to_posts_table
  • create_post_tag_pivot_table
3

Make Migrations Reversible

Always implement the down() method to reverse changes:
public function down(): void
{
    $this->getSchema()->drop('users');
}
4

Use Soft Deletes

For data that might need recovery, use soft deletes:
$table->softDeletes();
5

Add Indexes for Performance

Index columns used in WHERE clauses and JOINs:
$table->string('email')->unique();
$table->index(['user_id', 'created_at']);

Migration Workflow

1

Create Migration

$migrator = new Migrator();
$migrator->make('create_users_table');
2

Define Schema

Edit the generated file to define your table structure
3

Run Migration

$migrator->run();
4

Test Your Changes

Verify the table was created correctly
5

Commit to Version Control

Commit the migration file to your repository

Database Drivers

Lyger migrations work with multiple database drivers:
Default driver, no additional configuration needed:
.env
DB_CONNECTION=sqlite
DB_DATABASE=database/database.sqlite
The Schema builder automatically adapts to your configured database driver, handling differences in SQL syntax and data types.

Next Steps

Eloquent Models

Learn how to interact with your migrated tables

Relationships

Define relationships between tables