Migrations are like version control for your database, allowing you to define and share your application’s database schema. They work hand-in-hand with Laravel’s schema builder to build your application’s database structure.
Creating Migrations
Generate a new migration using Artisan:
# Create a migration for a new table
php artisan make:migration create_users_table
# Create a migration to modify a table
php artisan make:migration add_votes_to_users_table
Migrations are stored in database/migrations. Each filename contains a timestamp allowing Laravel to determine the order of migrations.
Migration Structure
A migration class contains two methods: up and down:
use Illuminate\Database\Migrations\ Migration ;
use Illuminate\Database\Schema\ Blueprint ;
use Illuminate\Support\Facades\ Schema ;
return new class extends Migration
{
public function up () : void
{
// Create or modify tables
}
public function down () : void
{
// Reverse the migration
}
};
Creating Tables
Users Table Migration
Here’s the complete users table migration from a fresh Laravel installation:
database/migrations/0001_01_01_000000_create_users_table.php
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 ( 'users' , function ( Blueprint $table ) {
$table -> id ();
$table -> string ( 'name' );
$table -> string ( 'email' ) -> unique ();
$table -> timestamp ( 'email_verified_at' ) -> nullable ();
$table -> string ( 'password' );
$table -> rememberToken ();
$table -> timestamps ();
});
Schema :: create ( 'password_reset_tokens' , function ( Blueprint $table ) {
$table -> string ( 'email' ) -> primary ();
$table -> string ( 'token' );
$table -> timestamp ( 'created_at' ) -> nullable ();
});
Schema :: create ( 'sessions' , function ( Blueprint $table ) {
$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 ();
});
}
/**
* Reverse the migrations.
*/
public function down () : void
{
Schema :: dropIfExists ( 'users' );
Schema :: dropIfExists ( 'password_reset_tokens' );
Schema :: dropIfExists ( 'sessions' );
}
};
This single migration creates three tables: users, password_reset_tokens, and sessions. The down method drops all three tables.
Cache Tables Migration
database/migrations/0001_01_01_000001_create_cache_table.php
return new class extends Migration
{
public function up () : void
{
Schema :: create ( 'cache' , function ( Blueprint $table ) {
$table -> string ( 'key' ) -> primary ();
$table -> mediumText ( 'value' );
$table -> integer ( 'expiration' ) -> index ();
});
Schema :: create ( 'cache_locks' , function ( Blueprint $table ) {
$table -> string ( 'key' ) -> primary ();
$table -> string ( 'owner' );
$table -> integer ( 'expiration' ) -> index ();
});
}
public function down () : void
{
Schema :: dropIfExists ( 'cache' );
Schema :: dropIfExists ( 'cache_locks' );
}
};
Jobs Tables Migration
database/migrations/0001_01_01_000002_create_jobs_table.php
return new class extends Migration
{
public function up () : void
{
Schema :: create ( 'jobs' , function ( Blueprint $table ) {
$table -> id ();
$table -> string ( 'queue' ) -> index ();
$table -> longText ( 'payload' );
$table -> unsignedTinyInteger ( 'attempts' );
$table -> unsignedInteger ( 'reserved_at' ) -> nullable ();
$table -> unsignedInteger ( 'available_at' );
$table -> unsignedInteger ( 'created_at' );
});
Schema :: create ( 'job_batches' , function ( Blueprint $table ) {
$table -> string ( 'id' ) -> primary ();
$table -> string ( 'name' );
$table -> integer ( 'total_jobs' );
$table -> integer ( 'pending_jobs' );
$table -> integer ( 'failed_jobs' );
$table -> longText ( 'failed_job_ids' );
$table -> mediumText ( 'options' ) -> nullable ();
$table -> integer ( 'cancelled_at' ) -> nullable ();
$table -> integer ( 'created_at' );
$table -> integer ( 'finished_at' ) -> nullable ();
});
Schema :: create ( 'failed_jobs' , function ( Blueprint $table ) {
$table -> id ();
$table -> string ( 'uuid' ) -> unique ();
$table -> text ( 'connection' );
$table -> text ( 'queue' );
$table -> longText ( 'payload' );
$table -> longText ( 'exception' );
$table -> timestamp ( 'failed_at' ) -> useCurrent ();
});
}
public function down () : void
{
Schema :: dropIfExists ( 'jobs' );
Schema :: dropIfExists ( 'job_batches' );
Schema :: dropIfExists ( 'failed_jobs' );
}
};
The jobs migration creates tables for Laravel’s queue system, including support for job batches and failed job tracking.
Column Types
The schema builder supports many column types:
Numeric
String
Date & Time
Boolean & Other
$table -> id (); // Auto-incrementing BIGINT
$table -> bigInteger ( 'votes' );
$table -> integer ( 'votes' );
$table -> smallInteger ( 'votes' );
$table -> tinyInteger ( 'votes' );
$table -> decimal ( 'amount' , 8 , 2 );
$table -> double ( 'amount' , 8 , 2 );
$table -> float ( 'amount' , 8 , 2 );
// Unsigned variants
$table -> unsignedBigInteger ( 'votes' );
$table -> unsignedInteger ( 'votes' );
$table -> unsignedTinyInteger ( 'attempts' );
$table -> string ( 'name' ); // VARCHAR(255)
$table -> string ( 'email' , 100 ); // VARCHAR(100)
$table -> text ( 'description' );
$table -> mediumText ( 'content' );
$table -> longText ( 'payload' );
$table -> char ( 'code' , 4 );
$table -> uuid ( 'id' );
$table -> date ( 'created_date' );
$table -> datetime ( 'created_at' );
$table -> timestamp ( 'created_at' );
$table -> timestamps (); // created_at & updated_at
$table -> time ( 'sunrise' );
$table -> year ( 'birth_year' );
$table -> boolean ( 'confirmed' );
$table -> json ( 'options' );
$table -> jsonb ( 'options' ); // PostgreSQL
$table -> binary ( 'data' );
$table -> enum ( 'status' , [ 'pending' , 'approved' ]);
Column Modifiers
Modify column definitions:
$table -> string ( 'email' ) -> nullable ();
$table -> string ( 'email' ) -> unique ();
$table -> string ( 'name' ) -> default ( 'Guest' );
$table -> timestamp ( 'created_at' ) -> useCurrent ();
$table -> integer ( 'votes' ) -> unsigned ();
$table -> string ( 'description' ) -> comment ( 'User description' );
Chain multiple modifiers:
$table -> string ( 'email' ) -> unique () -> nullable ();
$table -> timestamp ( 'email_verified_at' ) -> nullable ();
Indexes
Add database indexes:
Primary Key
Unique Index
Regular Index
Full Text (MySQL/PostgreSQL)
$table -> id (); // Auto-incrementing primary key
$table -> string ( 'email' ) -> primary ();
Foreign Keys
Define foreign key constraints:
Simple Foreign Key
$table -> foreignId ( 'user_id' ) -> constrained ();
This creates a user_id column and adds a foreign key constraint to the users table’s id column.
Custom Foreign Keys
$table -> foreignId ( 'user_id' )
-> constrained ()
-> onUpdate ( 'cascade' )
-> onDelete ( 'cascade' );
$table -> foreignId ( 'user_id' )
-> constrained ( 'users' )
-> onDelete ( 'set null' );
Real example from the sessions table:
database/migrations/0001_01_01_000000_create_users_table.php
$table -> foreignId ( 'user_id' ) -> nullable () -> index ();
Dropping Foreign Keys
$table -> dropForeign ([ 'user_id' ]);
$table -> dropForeign ( 'posts_user_id_foreign' );
Modifying Tables
Adding Columns
Schema :: table ( 'users' , function ( Blueprint $table ) {
$table -> string ( 'phone' ) -> nullable ();
$table -> integer ( 'votes' ) -> default ( 0 );
});
Modifying Columns
Schema :: table ( 'users' , function ( Blueprint $table ) {
$table -> string ( 'name' , 100 ) -> change ();
$table -> integer ( 'votes' ) -> unsigned () -> default ( 0 ) -> change ();
});
Renaming Columns
Schema :: table ( 'users' , function ( Blueprint $table ) {
$table -> renameColumn ( 'from' , 'to' );
});
Dropping Columns
Schema :: table ( 'users' , function ( Blueprint $table ) {
$table -> dropColumn ( 'votes' );
$table -> dropColumn ([ 'votes' , 'avatar' ]);
});
Dropping Tables
Schema :: drop ( 'users' );
Schema :: dropIfExists ( 'users' );
Real example:
database/migrations/0001_01_01_000000_create_users_table.php
public function down () : void
{
Schema :: dropIfExists ( 'users' );
Schema :: dropIfExists ( 'password_reset_tokens' );
Schema :: dropIfExists ( 'sessions' );
}
Running Migrations
Run Migrations
Rollback Last Batch
Rollback All
Fresh Migration
Refresh Database
The migrate:fresh command will drop all tables and re-run all migrations. Never use this in production!
Migration Status
Check which migrations have run:
php artisan migrate:status
Squashing Migrations
Combine multiple migrations into a single file:
php artisan schema:dump
# Dump and delete migrations
php artisan schema:dump --prune
Squashing migrations is useful when you have many migration files and want to reduce clutter while preserving the schema.