Database migrations are one of the most powerful features of the Laravel framework. They provide a structured, version-controlled approach to managing database tables, columns, indexes, and relationships. Instead of manually creating tables in a database management tool or writing raw SQL, migrations allow you to define your database structure using PHP code. This makes your project consistent, portable, and maintainable.
In modern application development, the database layer is just as important as the application logic. Keeping track of changes, synchronizing schema updates among developers, and deploying updates across multiple environments can become challenging without a form of version control. Laravel migrations solve these problems by providing a clean, robust system for defining, modifying, and sharing database schema changes.
This comprehensive guide explains the concept of migrations, how to create them, how to run them, and how they fit into Laravel’s overall workflow. It expands the example you gave into a full-length explanation suitable for beginners and intermediate developers looking to master Laravel database migrations.
What Are Database Migrations
A database migration in Laravel is a PHP class that defines changes you want to make to your database. These changes may include creating new tables, adding columns, modifying existing structures, or establishing relationships between tables. Each migration acts as a version of the database structure.
For example, if you create a migration called create_products_table, it becomes part of a documented history of your database schema. Anyone working on the same project can run the migrations to synchronize their database structure with yours.
Migrations eliminate the need for manually writing SQL queries for table creation and editing. They turn database updates into an orderly and trackable process. This keeps your database schema clean, consistent, and easy to manage.
Why Migrations Are Essential in Modern Development
Modern web applications rely heavily on database consistency. Without a version-controlled system like migrations, developers often run into problems such as inconsistent tables, missing columns, mismatched data types, or database errors across environments.
Migrations solve these problems by providing:
A historical record of changes
A rollback system
A shareable database structure
A clean environment for new team members
A safe method to modify live databases
When working solo, migrations help you keep track of database changes between development phases. In large teams, migrations are indispensable for maintaining structure across environments like development, staging, and production.
The Role of Artisan in Managing Migrations
Laravel’s command-line tool, Artisan, plays an essential role in migrations. You use Artisan commands to create new migration files, run them, roll them back, and refresh or reset the database.
Your example command:
php artisan make:migration create_products_table
creates a migration file with a timestamp and descriptive name. Artisan immediately places it in the database/migrations directory. This timestamp ensures migrations run in the correct order, even when multiple migrations exist.
Artisan also provides commands like:
php artisan migrate
php artisan migrate:rollback
php artisan migrate:refresh
php artisan migrate:reset
php artisan migrate:fresh
These commands manage how migrations are executed, undone, or rebuilt.
Understanding the Structure of a Migration File
Every migration file contains two essential methods: up and down.
The up method defines the changes you want to apply to the database, such as creating a table or adding a column. The down method defines how to undo those changes, serving as a rollback mechanism.
A typical migration file looks like this:
public function up()
{
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->integer('price');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('products');
}
The up method runs when you execute php artisan migrate. The down method runs during a rollback, allowing you to revert changes safely.
Creating a Migration Using Artisan
To create a new migration, use the Artisan command:
php artisan make:migration create_products_table
The migration file will be generated inside:
database/migrations
The timestamp attached to the file name ensures that migrations run sequentially, reflecting the chronological order in which they were created.
The naming convention also hints at the migration’s purpose. In this example, create_products_table clearly signals an intention to create a new table named products.
Following descriptive naming conventions is essential, especially in large projects.
Defining Table Columns in a Migration
Once the migration file is created, the Schema builder allows you to define columns inside the table. Laravel supports a wide variety of column types such as:
string
text
integer
decimal
boolean
date
datetime
timestamp
json
uuid
foreignId
Your migration may look like this:
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->decimal('price', 8, 2);
$table->integer('stock')->default(0);
$table->timestamps();
});
Laravel gives you expressive methods for defining table structures. The Blueprint object handles all column definitions and constraints.
Understanding Table Indexes and Keys
Migrations not only allow you to define columns but also indexes, primary keys, and foreign keys. For example:
$table->primary('id');
$table->unique('sku');
$table->index('category_id');
$table->foreign('category_id')->references('id')->on('categories');
Indexes improve query performance, unique ensures no duplicates, and foreign keys maintain relational integrity.
Laravel also offers foreignId shorthand:
$table->foreignId('category_id')->constrained();
This automatically creates a foreign key referencing categories.id.
Running Migrations with php artisan migrate
To apply all pending migrations, run:
php artisan migrate
Laravel will:
Check which migrations have not been executed
Run the up methods of those migrations
Record them in the migrations table
The migrations table lives in your database. Laravel uses this table to track which migration files have already been executed. This ensures migrations are never accidentally run twice.
If all migrations run successfully, Laravel updates the migrations table with a new entry for each migration file.
Rolling Back Migrations
Laravel provides rollback capabilities so you can undo the most recent batch of migrations:
php artisan migrate:rollback
This command triggers the down method of the most recently executed migration batch. A batch refers to all migrations run during a single migrate operation.
Rollback is very useful when:
Testing changes
Fixing mistakes
Undoing errors during development
Working with unstable database structures
Resetting and Refreshing the Database
In certain cases, you may want to rebuild the entire database. Laravel provides several commands for this.
Reset all migrations:
php artisan migrate:reset
This undoes all migrations by running all down methods.
To reset and run migrations again:
php artisan migrate:refresh
This is often used during development to rebuild the database cleanly.
To drop all tables and start fresh:
php artisan migrate:fresh
This deletes all tables and runs migrations without calling any rollback methods.
Modifying Existing Tables with Migrations
Migrations can modify existing tables as well. For example, to add a column:
php artisan make:migration add_stock_to_products_table
Inside the migration:
public function up()
{
Schema::table('products', function (Blueprint $table) {
$table->integer('stock')->default(0);
});
}
public function down()
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('stock');
});
}
Using Schema::table allows you to update an existing table without recreating it.
You can also modify indexes, foreign keys, or column types.
Best Practices for Naming Migrations
Laravel developers follow naming conventions to maintain structure and clarity.
Popular naming patterns include:
create_products_table
add_price_to_products_table
add_details_to_users_table
remove_old_columns_from_orders_table
update_status_in_invoices_table
Using descriptive names makes migrations self-explanatory.
Version Control and Team Collaboration
Because migrations are plain code files, they integrate perfectly with version control systems like Git. When working in teams, you commit your migrations, push them to the repository, and other team members run them on their local machines.
This prevents the common issue where one developer adds a column but forgets to update the rest of the team. With migrations, every change is tracked and shareable.
Additionally, databases in development, staging, and production remain consistent because they all rely on the same migration history.
Migration Timestamps and Execution Order
Every migration file contains a timestamp as a prefix. This ensures that files run in the order they were created.
For example:
2025_06_14_154300_create_products_table.php
2025_06_14_155010_add_stock_to_products_table.php
Even if they were added minutes apart, the timestamp guarantees their intended sequence. This prevents dependency issues when one migration needs another table to exist first.
Handling Soft Deletes in Migrations
Laravel supports soft deletes with a simple column:
$table->softDeletes();
This adds a deleted_at timestamp column instead of actually deleting data. To undo a soft delete, you simply restore the record.
Soft deletes are commonly used in applications that require record recovery.
Adding Timestamps Automatically
By default, Laravel adds created_at and updated_at columns using:
$table->timestamps();
These timestamps are automatically managed by Eloquent models. They represent when a record was created or updated.
Dropping and Renaming Tables
To drop a table:
Schema::drop(‘products’);
Or to drop only if it exists:
Schema::dropIfExists(‘products’);
Renaming a table:
Schema::rename(‘old_name’, ‘new_name’);
Migrations allow you to restructure tables safely without manually altering the database.
Database Drivers and Compatibility
Laravel migrations support multiple database systems, including:
MySQL
PostgreSQL
SQLite
SQL Server
The Schema builder handles SQL differences behind the scenes. This means you write the same migration code regardless of database type.
This makes migration files portable and deployable across different environments.
Using Raw SQL in Migrations
In rare cases, you may need full control over raw SQL. Laravel provides DB::statement for this:
DB::statement(‘ALTER TABLE products MODIFY price DECIMAL(10,2)’);
Raw SQL should be used sparingly, but Laravel allows flexibility when necessary.
Seeding Data Alongside Migrations
Migrations work alongside seeders. Seeders populate the database with test or default data.
Example:
php artisan db:seed
or:
php artisan migrate –seed
This runs migrations and seeds the database in one command.
Combining Migrations with Factories
Factories generate sample data for testing. They complement migrations during development. After running migrations, you can populate a table using factories.
Example:
Product::factory()->count(50)->create();
This quickly fills your database with realistic test data.
Migrations in Deployment Workflows
Production deployments often involve running migrations automatically. When updating an application, new migrations are executed to reflect the latest database structure.
Migrations ensure:
Smooth deployments
No outdated schema
Reliable production databases
Common Errors and Troubleshooting Migration Issues
Common mistakes include:
Mismatched table names
Missing down methods
Foreign key constraints failing
Running migrations without refreshing caches
Troubleshooting involves checking logs, reviewing migration order, and verifying database configuration.
The Relationship Between Migrations and Eloquent Models
Eloquent models rely on database tables created through migrations. When building features, developers typically:
Create a migration
Define the model
Add relationships
Build controllers
Create views
Leave a Reply