The make:migration command generates a new migration file in the database/migrations directory.
Syntax
php rawr make:migration < nam e >
Descriptive name for the migration (use snake_case)
Basic Usage
Create a new migration:
php rawr make:migration create_users_table
Output:
Migration created: database/migrations/2026_03_08_143022_create_users_table.php
Migration filenames are automatically prefixed with a timestamp to ensure they run in the correct order.
Generated Migration
The command creates a migration class with up() and down() methods:
database/migrations/2026_03_08_143022_create_users_table.php
<? php
declare ( strict_types = 1 );
use Lyger\Database\ Migration ;
class CreateUsersTable extends Migration
{
public function up () : void
{
$this -> getSchema () -> create ( 'create_users_table' , function ( $table ) {
$table -> id ();
});
}
public function down () : void
{
$this -> getSchema () -> drop ( 'create_users_table' );
}
}
Migration Structure
Class Name
The class name is automatically generated from your migration name:
Migration Name Generated Class Name create_users_tableCreateUsersTableadd_role_to_usersAddRoleToUsersupdate_products_tableUpdateProductsTable
$className = str_replace ([ '-' , '_' ], '' , ucwords ( $name , '-_' ));
Timestamp Prefix
Each migration gets a unique timestamp:
$timestamp = date ( 'Y_m_d_His' );
$filename = $timestamp . '_' . $name . '.php' ;
Example: 2026_03_08_143022_create_users_table.php
up() Method
Defines the changes to apply:
public function up () : void
{
$this -> getSchema () -> create ( 'users' , function ( $table ) {
$table -> id ();
$table -> string ( 'name' );
$table -> string ( 'email' ) -> unique ();
$table -> timestamp ( 'created_at' ) -> nullable ();
});
}
down() Method
Defines how to reverse the changes:
public function down () : void
{
$this -> getSchema () -> drop ( 'users' );
}
Naming Conventions
Table Creation
For creating new tables:
php rawr make:migration create_products_table
php rawr make:migration create_orders_table
Column Addition
For adding columns to existing tables:
php rawr make:migration add_status_to_orders
php rawr make:migration add_price_and_stock_to_products
Column Modification
For modifying existing columns:
php rawr make:migration update_users_email_nullable
php rawr make:migration change_price_to_decimal_in_products
Table Deletion
For dropping tables:
php rawr make:migration drop_old_logs_table
php rawr make:migration remove_deprecated_tables
Use descriptive names that clearly indicate what the migration does. This makes it easier to understand your database history.
Common Migration Patterns
Create Table with Columns
<? php
declare ( strict_types = 1 );
use Lyger\Database\ Migration ;
class CreateProductsTable extends Migration
{
public function up () : void
{
$this -> getSchema () -> create ( 'products' , function ( $table ) {
$table -> id ();
$table -> string ( 'name' );
$table -> text ( 'description' ) -> nullable ();
$table -> decimal ( 'price' , 10 , 2 );
$table -> integer ( 'stock' ) -> default ( 0 );
$table -> boolean ( 'active' ) -> default ( true );
$table -> timestamps ();
});
}
public function down () : void
{
$this -> getSchema () -> drop ( 'products' );
}
}
Add Columns to Existing Table
<? php
declare ( strict_types = 1 );
use Lyger\Database\ Migration ;
class AddCategoryToProducts extends Migration
{
public function up () : void
{
$this -> getSchema () -> table ( 'products' , function ( $table ) {
$table -> foreignId ( 'category_id' ) -> nullable () -> constrained ();
$table -> string ( 'sku' ) -> unique ();
});
}
public function down () : void
{
$this -> getSchema () -> table ( 'products' , function ( $table ) {
$table -> dropForeign ([ 'category_id' ]);
$table -> dropColumn ([ 'category_id' , 'sku' ]);
});
}
}
Create Pivot Table
<? php
declare ( strict_types = 1 );
use Lyger\Database\ Migration ;
class CreateOrderProductTable extends Migration
{
public function up () : void
{
$this -> getSchema () -> create ( 'order_product' , function ( $table ) {
$table -> id ();
$table -> foreignId ( 'order_id' ) -> constrained () -> onDelete ( 'cascade' );
$table -> foreignId ( 'product_id' ) -> constrained () -> onDelete ( 'cascade' );
$table -> integer ( 'quantity' ) -> default ( 1 );
$table -> decimal ( 'price' , 10 , 2 );
$table -> timestamps ();
});
}
public function down () : void
{
$this -> getSchema () -> drop ( 'order_product' );
}
}
Add Indexes
<? php
declare ( strict_types = 1 );
use Lyger\Database\ Migration ;
class AddIndexesToUsers extends Migration
{
public function up () : void
{
$this -> getSchema () -> table ( 'users' , function ( $table ) {
$table -> index ( 'email' );
$table -> index ([ 'last_name' , 'first_name' ]);
$table -> unique ( 'username' );
});
}
public function down () : void
{
$this -> getSchema () -> table ( 'users' , function ( $table ) {
$table -> dropIndex ([ 'email' ]);
$table -> dropIndex ([ 'last_name' , 'first_name' ]);
$table -> dropUnique ([ 'username' ]);
});
}
}
Modify Columns
<? php
declare ( strict_types = 1 );
use Lyger\Database\ Migration ;
class UpdateUsersEmailNullable extends Migration
{
public function up () : void
{
$this -> getSchema () -> table ( 'users' , function ( $table ) {
$table -> string ( 'email' ) -> nullable () -> change ();
$table -> string ( 'phone' , 20 ) -> after ( 'email' );
});
}
public function down () : void
{
$this -> getSchema () -> table ( 'users' , function ( $table ) {
$table -> string ( 'email' ) -> nullable ( false ) -> change ();
$table -> dropColumn ( 'phone' );
});
}
}
Available Column Types
The schema builder supports various column types:
Numeric
$table -> id (); // Auto-incrementing primary key
$table -> integer ( 'votes' ); // INTEGER
$table -> bigInteger ( 'votes' ); // BIGINT
$table -> decimal ( 'amount' , 8 , 2 ); // DECIMAL with precision
$table -> float ( 'amount' , 8 , 2 ); // FLOAT
$table -> double ( 'amount' , 8 , 2 ); // DOUBLE
String
$table -> string ( 'name' ); // VARCHAR(255)
$table -> string ( 'name' , 100 ); // VARCHAR(100)
$table -> text ( 'description' ); // TEXT
$table -> char ( 'code' , 10 ); // CHAR(10)
Date/Time
$table -> date ( 'birth_date' ); // DATE
$table -> dateTime ( 'created_at' ); // DATETIME
$table -> timestamp ( 'updated_at' ); // TIMESTAMP
$table -> timestamps (); // created_at + updated_at
$table -> time ( 'sunset' ); // TIME
Other
$table -> boolean ( 'active' ); // BOOLEAN
$table -> json ( 'options' ); // JSON
$table -> binary ( 'data' ); // BLOB
$table -> enum ( 'status' , [ 'pending' , 'active' , 'inactive' ]);
Column Modifiers
Customize columns with modifiers:
$table -> string ( 'email' ) -> unique (); // Unique constraint
$table -> string ( 'name' ) -> nullable (); // Allow NULL
$table -> integer ( 'votes' ) -> default ( 0 ); // Default value
$table -> string ( 'role' ) -> default ( 'user' ); // String default
$table -> timestamp ( 'created_at' ) -> useCurrent (); // Current timestamp
$table -> string ( 'bio' ) -> after ( 'email' ); // Column order
$table -> text ( 'description' ) -> comment ( 'User bio' ); // Add comment
Foreign Keys
Define relationships between tables:
// Simple foreign key
$table -> foreignId ( 'user_id' ) -> constrained ();
// Custom foreign key
$table -> foreignId ( 'author_id' )
-> constrained ( 'users' )
-> onDelete ( 'cascade' )
-> onUpdate ( 'cascade' );
// Drop foreign key
$table -> dropForeign ([ 'user_id' ]);
Error Handling
Missing Name
Output:
Error: Migration name required
Directory Creation
The command automatically creates the migrations directory if it doesn’t exist:
$migrationsPath = $basePath . '/database/migrations' ;
if ( ! is_dir ( $migrationsPath )) {
mkdir ( $migrationsPath , 0755 , true );
}
Source Code
The migration generation logic:
function makeMigration ( string $basePath , ? string $name ) : void
{
if ( $name === null ) {
echo "Error: Migration name required \n " ;
exit ( 1 );
}
$migrationsPath = $basePath . '/database/migrations' ;
if ( ! is_dir ( $migrationsPath )) {
mkdir ( $migrationsPath , 0755 , true );
}
$timestamp = date ( 'Y_m_d_His' );
$filename = $timestamp . '_' . $name . '.php' ;
$migrationPath = $migrationsPath . '/' . $filename ;
$className = str_replace ([ '-' , '_' ], '' , ucwords ( $name , '-_' ));
$content = <<< PHP
<?php
declare(strict_types=1);
use Lyger\Database\Migration;
class { $className } extends Migration
{
public function up(): void
{
\$ this->getSchema()->create('{ $name }', function ( \$ table) {
\$ table->id();
});
}
public function down(): void
{
\$ this->getSchema()->drop('{ $name }');
}
}
PHP ;
file_put_contents ( $migrationPath , $content );
echo "Migration created: database/migrations/{ $filename } \n " ;
}
Workflow Example
Step 1: Create Migration
Step 2: Define Schema
Step 3: Run Migration
Step 4: Verify
php rawr make:migration create_posts_table
Best Practices
Do:
Use descriptive migration names
Always define both up() and down() methods
Keep migrations small and focused
Test rollbacks to ensure they work
Don’t:
Modify existing migrations after they’ve been run
Delete migration files
Put business logic in migrations
Use raw SQL unless absolutely necessary
Next Steps
Run Migrations Execute your migrations
Make Model Create models for your tables
Schema Builder Learn about schema building
Seeding Populate your database with test data