- What are Laravel Migrations?
- When to use Laravel migrations and why
- Tutorial prerequisites
- Setting up the Laravel project
- How to create a migration
- How to structure your Laravel migrations
- Running your migration
- How to create, modify, and drop tables
- How to use columns within Laravel migrations
- How to use indexes within Laravel migrations
- How to remove migrations
- How to squash migrations
- How to use migration events
- Final thoughts
GSD
How to Create, Run, & Manage Laravel Migrations
Posted by Funke Olasupo on January 26, 2024
Updated Jan 2024 by Deborah Emeni.
Database schemas can be modified via migrations, such as adding or removing tables and altering types and constraints. For instance, when adjusting application requirements in a team project, you might want to consider altering the database. Typically, an updated .sql file is shared, but this approach is neither ideal nor scalable, as errors like forgetting to import or using an outdated version may occur.
Migrations act like version control for your database, allowing your team to share and define the schema of your application's database. Laravel migrations are essential because they help you tackle such database collaboration issues for a team. Your team members won't need to manually update their database whenever they pull your changes from source control.
In this tutorial, you will learn the importance of migrations in your Laravel application and the various benefits of Laravel migrations and use cases. You will also learn multiple migration actions, commands, and events by creating migrations for a sample Laravel project.
If you’re just getting started with Laravel, but still want to try out ButterCMS, check out our Laravel starter project to jumpstart your development.
Table of contents
What are Laravel Migrations?
Migrations streamline database schema updates, allowing development teams to implement, adjust, and manage changes collectively. They handle tasks like creation, deletion, and modification without manual SQL queries, supporting five databases, including MariaDB 10.10+, MySQL 5.7+, PostgreSQL 11.0+, SQLite 3.8.8+, and SQL Server 2017+.
In Laravel 10.x, several significant improvements have been made to the previous version, including argument and return types for application skeleton methods, updates to stub files, a developer-friendly process abstraction layer, Laravel Pennant for feature flags, and removal of unnecessary doc block type hints. This current version requires PHP 8.1 or later. Check the documentation for more details.
When to use Laravel migrations and why
You can leverage Laravel migrations whenever your development team needs to modify your application’s database schema. For example, if you want to create new tables, add columns, modify existing ones, or drop an entire table, Laravel migrations provide an easier way to collaborate with your team. It also offers version control for your schema and a secure method of managing database changes without having to write SQL queries directly.
Aside from version control, migrations simplify automation, aid in structuring models, and support developers with environment organization and secure schema modifications. Migrations in Laravel allow altering database fields without deleting existing records, enabling the database to evolve with changing application requirements.
Tutorial prerequisites
To follow along with this tutorial, ensure you have the following:
-
PHP installed (version 8.2)
-
Terminal or a CLI
-
Code editor (VS Code)
Note that the operating system used in this tutorial is MacOS, and if you use MacOS, you can install PHP and Composer in minutes via Laravel Herd.
Setting up the Laravel project
Let’s create a new Laravel project that you will use to create, use, and manage Laravel migrations. Using the Composer, run the following command:
composer create-project laravel/laravel voyage-app
Open the project in your code editor and start the local development server by running the following command:
php artisan serve
After running the command, your application will start on localhost:8000, opening in your browser as follows:
Setting up the database
Now that your application is running, you can set up your database. For this tutorial, use SQLite 3.8.8+ and create a new SQLite database in your project’s root directory by running the following command:
touch database/database.sqlite
Once you run that command, a database.sqlite file will be created in your database directory.
How to create a migration
Laravel provides an Artisan command called make:migration that you can use to generate a migration file for your database, saving you the stress of doing it manually. Each migration file defines a table in your database containing the schema to create or modify the table in the database.
In your project’s directory, run the following command to create a migration file:
php artisan make:migration create_users_table
Once you run the command, you’ll see the following in your terminal:
The command creates a migration file with a timestamp prefix in the database/migrations directory. Laravel attempts to determine the table name and migration action from the filename, pre-filling the migration file. If unsuccessful, you can manually define the table name. Use the --path argument with make:migration to specify a custom path in your application for the generated migration:
php artisan make:migration create_users_table --path=app/database/migrations
Laravel migration generators to help streamline your migration
Laravel’s built-in migration tools are powerful for use in your applications. However, relying solely on Laravel's built-in migration tools might become cumbersome when building complex projects. As a solution, you can streamline your migration process by utilizing third-party Laravel migration generators as follows:
-
This Laravel migration generator automatically analyzes your database and generates tables and columns along with their data types. It can examine your existing database to help reduce development time and minimize manual errors. Additionally, it includes indexes and generates foreign key constraints based on the existing relationships between your tables.
-
This Laravel migration generator is particularly useful for projects with numerous migrations that alter tables using ->change() from doctrine/dbal, which SQLite doesn’t support. It provides a solution for updating table structures in SQLite for use in tests. It is also useful for projects without databases and migrations, enabling the database's transformation into base migrations.
How to structure your Laravel migrations
The migration file includes an up()
method for executing changes during migration (e.g., table creation) and a down()
method for rolling back changes. For instance, up()
might create a table, while down()
removes it. The Schema Builder manages table creation/modification using these methods.
Let’s explore the structure of a migration file by defining one to create a users table.
The first step is to add the PHP start tag <?php
at the top of your file, specifying that the code below will be written in PHP. Then, proceed with the following:
-
Import the Migration and Blueprint classes that define the structure of the migration.
-
Import the Schema facade, which provides access to the Schema builder.
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
Next, define an anonymous class that extends the Migration class with the following:
return new class extends Migration {}
Next, create a method that defines the logic for creating the database table as follows:
return new class extends Migration {
public function up(): void {}
}
Then, under the up()
method, define the users table to include an auto-incrementing integer primary key column named id, along with two columns, created_at and updated_at, for timestamping purposes with the following:
return new class extends Migration {
public function up(): void {}
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
}
Next, add the down()
method that defines the logic for reversing the migration with the following:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
public function up(): void {}
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->timestamps();
});
public function down(): void
{
Schema::dropIfExists('users');
}
}
};
Running your migration
Before running migrations, ensure your database connection is set up correctly. Update the .env file in your application's root directory with the database configuration, replacing the placeholders with your database details following the example below:
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=<Enter your database name>
DB_USERNAME=<Enter your database username>
DB_PASSWORD=<Enter your database password>
Next, run your migrations by using the Artisan migrate command to execute all pending migrations and update the database as follows:
php artisan migrate
After defining the migration, your database should have a users table with columns: id, created_at, and updated_at, generated by the timestamps()
in your migration file:
Also, when deploying your application across multiple servers, ensure that your migrations run exclusively on one server at a time to avoid conflicts. Use the --isolated option when running your migrations as shown below:
php artisan migrate --isolated
When running migrations for your production database, you may encounter some that are destructive and could accidentally cause data loss. To avoid this, you'll be prompted to confirm your migration commands before execution. However, if you choose to skip this safety measure and execute them directly, you can use the --force flag when running your migration as follows:
php artisan migrate --force
How to create, modify, and drop tables
With Laravel Migrations, you can simplify and streamline your database schema management, making creating, modifying, and dropping tables easier. Rather than writing complex and error-prone SQL queries, you can leverage the methods provided by Laravel Migrations as discussed below:
Creating tables for Laravel migrations
A table is a collection of data objects in a database organized in a tabular format consisting of rows and columns. You can use the Schema facade's create()
method to create a new database table by providing the table name and a closure defining the table using a Blueprint
object.
In your migration file, replace the code in the up()
function with the code to create a new users table. If the users table exists, run php artisan migrate:rollback before creating the new table:
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('role');
$table->timestamps();
});
In the code snippet above, the Schema::create('users', function($table) {...}
defines and modifies columns for the users table, namely the id, name, string, and timestamps (created_at and updated_at). Run the migration with the command:
php artisan migrate
After running the command, you'll see in the terminal that your users table was created successfully:
Check your database for the table with the specified columns:
Also, to check if a table or column exists and perform a specific action afterward, you can use the hasTable()
and hasColumn()
methods:
if (Schema::hasTable('users')) {
// The "users" table exists...
}
if (Schema::hasColumn('users', 'role')) {
// The "users" table exists and has an "email" column...
}
Modifying tables for Laravel migrations
Laravel migrations provide methods like rename
in the Schema facade to modify tables. Ensure explicit foreign key constraint names in your migration file when renaming to avoid referencing the old table.
To rename a table, use the format $from for the current name and $to for the new name:
Schema::rename($from, $to);
Create another migration file called create_fund_users_table and use the rename()
method to rename the users table to fund_users:
return new class extends Migration
{
public function up(): void
{
Schema::rename('users', 'fund_users');
}
}
Run the migration using the migrate command, and then check if the users table in your database has been renamed to fund_users:
How to drop tables
To reverse table creation, you can use the Schema facade's drop/dropIfExists
method in a migration file's down()
method. The snippet below drops the users table, and Schema::dropIfExists
checks if the table exists before dropping it, continuing execution if it doesn't. To test, replace users with students in the migration file for the users table:
public function down(): void
{
Schema::dropIfExists('students');
}
Then, run the following command to rollback the migration:
php artisan migrate:rollback
Despite the incorrect table name, running the rollback command will successfully roll back your migrations:
Replace Schema::dropIfExists with Schema::drop
to drop a non-existing table:
public function down(): void
{
Schema::drop('students');
}
When you run the migration rollback command, you'll encounter an error saying “no such table”:
How to use columns within Laravel migrations
In a relational database, each row corresponds to a column with data values of the same type, defining the data structure. Laravel offers methods for column creation, deletion, and modification.
How to create columns
To update existing tables with additional columns, use the table()
method in the Schema facade. It accepts the same arguments as the create()
method. You can select the column's data type from these available fields.
To add a wallet column to the fund_users table, create a migration file named add_wallet_column and include the following in the up()
method of the generated file:
public function up(): void
{
Schema::table('fund_users', function (Blueprint $table) {
$table->double('wallet');
});
}
Run the migration and check your database for the new column:
Important column types to remember
The Schema Builder Blueprint provides various methods for column types in your database tables. Here are some examples:
Column Types |
Description |
ENUM |
It's created with the |
BOOLEAN |
It's created with the |
BLOB |
It utilizes the |
DATETIME |
It uses the |
VARCHAR |
Created with the |
UNSIGNED INTEGER |
Created with the |
INTEGER |
Created with the |
DATE |
Created with the |
TIMESTAMP |
Created with the |
TIME |
It utilizes the |
Important column modifiers
Laravel migrations offer column modifiers to enhance data storage, allowing null entries, defining column order and default values, and generating values based on other columns. Let’s discuss some column modifiers in the table below:
Column Modifier |
Description |
->default($value) |
This sets the default value for a column during data insertion. |
->after('column') |
This specifies the position of a new column in an existing table. |
->nullable($value = true) |
This allows the specified column to store NULL values. |
->useCurrentOnUpdate() |
This updates the timestamp column to the current time when a table record is updated. |
->storedAs($expression) |
This defines stored generated columns. |
->from($integer) |
This sets the initial value for an auto-incrementing column. |
->autoIncrement() |
This automatically increments a column by one when inserting a new record. |
->comment('my comment') |
This adds descriptive notes as comments to your database schema. |
->generatedAs($expression) |
This generates a virtual column value using a specified expression. |
Laravel migrations allow setting initial values with Default Expressions during table creation, which can be simple values or complex functions. You can use these expressions to assign default values to JSON columns. Additionally, Laravel migrations provide control over Column Order, allowing precise positioning of new columns within tables for organized schema and aligned data.
How to drop a column
You can use the dropColumn()
method on the Schema facade to drop columns. Let’s try dropping the wallet column that we added. To do this, you need to create another migration file. In your migration file, under the up()
method, drop the wallet column with the following code:
public function up(): void
{
Schema::table('fund_users', function (Blueprint $table) {
$table->dropColumn('wallet');
});
}
Run the migration and check if the wallet column is removed from your database:
How to modify a column
To modify a column in SQLite, install the doctrine/dbal package using Composer by running the following command in your project directory:
composer require doctrine/dbal
After installation, modify your column using the timestamp()
method. Include the following configuration in your app's config/database.php file:
use Illuminate\Database\DBAL\TimestampType;
'dbal' => [
'types' => [
'timestamp' => TimestampType::class,
],
],
Create a new migration file and add the following to use the change()
method to modify the role column data type to string with a maximum length of 50 characters. Also, use the nullable()
method to make the column nullable, meaning it can accept NULL values:
Schema::table('fund_users', function (Blueprint $table) {
$table->string('role', 50)->nullable()->change();
});
Run the migrate command and verify that the role column in your database is updated to a maximum length of 50 characters:
Use the Schema facade's renameColumn()
method to rename the name column to full_name by adding the changes to a new migration file:
Schema::table('fund_users', function (Blueprint $table) {
$table->renameColumn('name', 'full_name');
});
Run the migrate command and verify the renaming of the name column to full_name in your database:
How to use indexes within Laravel migrations
Indexes enhance database query speed by quickly directing users to the needed data, acting as pointers to table information. However, it's best to be careful when using indexes, as frequent table updates lead to index regeneration, causing system slowdowns.
How to create indexes
An excellent example of an index type is unique, ensuring all values in a column are distinct. Create a migration file to make the email address column unique by adding the following:
Schema::table('fund_users', function (Blueprint $table) {
$table->string('email address')->unique();
});
Run your migration file and the email address column will be defined as unique in your database as follows:
Important index types
Other available index types are supported by Laravel. Let’s explore the index types below:
Index Types |
Description |
Primary Key |
|
Unique Index |
|
Non-Unique Index |
|
Fulltext Index |
|
Foreign Key |
|
If using older MySQL or MariaDB versions (before 5.7.7 and 10.2.2, respectively), manually configure the default string length in migrations by calling Schema::defaultStringLength
in AppServiceProvider's boot method. You can use the following to set the default length for string columns created in future migrations to 200 characters:
use Illuminate\Support\Facades\Schema;
public function boot(): void
{
Schema::defaultStringLength(200);
}
How to drop indexes
Laravel automatically assigns an index name based on the table's name, indexed column, and index type. To drop an index, specify the assigned name in the migration's up()
method:
public function up(): void
{
Schema::table('fund_users', function (Blueprint $table) {
$table->dropUnique('fund_users_email_address_unique');
});
}
The $table->dropUnique('fund_users_email_address_unique');
removes the unique index 'fund_users_email_address_unique' on the email address column, which includes the table name, column name, and key type.
Run your migrations and confirm the removal of the unique constraint by inserting a duplicate email_address without encountering errors.
How to rename indexes
The Schema Builder Blueprint provides a renameIndex
method requiring the current and new index names as arguments. Create a new migration file and add a unique()
index for a home address column:
public function up(): void
{
Schema::table('fund_users', function (Blueprint $table) {
$table->string('home address')->unique();
});
}
Verify the new indexed column in your database:
Rename the index by creating a new migration file. Add the following code to rename your existing index on the fund_users table from fund_users_home_address_unique to office_address:
public function down(): void
{
Schema::table('fund_users', function (Blueprint $table) {
$table->renameIndex('fund_users_home_address_unique', 'office_address');
});
}
How to handle foreign key constraints
Foreign key constraints in relational databases, like those in Laravel using the foreign()
and references()
methods, maintain data accuracy by ensuring values in a “foreign key” column exist in the corresponding “primary key” column of the referenced table.
To see this in action, create a new migration file for a books table called create_books_table and run the migrations:
Next, create a migration file called create_foreign_key_constraints_for_books_table to define foreign key constraints referencing the fund_users table. Follow Laravel's naming convention (tablename_columnname) and use the foreign()
method with references()
and on()
properties to specify the referenced column and table (fund_user_id referencing id in the fund_users table):
public function up(): void
{
Schema::table('books', function (Blueprint $table) {
$table->unsignedBigInteger('fund_user_id');
$table->foreign('fund_user_id')->references('id')->on('fund_users');
});
}
Run the migration to create the fund_user_id in your database:
In addition, Laravel provides a concise foreignId()
method, eliminating the need for declaring unsignedBigInteger
, references()
, on()
, or additional unsignedBigInteger
for the foreign key. You can also include other properties like constrained()
as follows:
Schema::create('books', function (Blueprint $table) {
$table->foreignId('fund_user_id')->constrained(table: 'fund_users', indexName: 'books_user_id')
->onUpdate('cascade')->onDelete('cascade');
});
The constrained()
method is optional for specifying a custom table name. Use onUpdate()
and onDelete()
to define actions for foreign key updates or deletions, with cascade deleting rows in the child table when the parent table's rows are deleted. Any additional column modifiers must precede constrained()
.
For example, the constrained()
method defines a foreign key relationship in the following code snippet. It specifies that the fund_user_id column in the books table references the primary key of the fund_users table:
Schema::create('books', function (Blueprint $table) {
$table->foreignId('fund_user_id')
->nullable()
->constrained('fund_users');
});
To remove a foreign key constraint, you can use Laravel’s dropForeign()
method by specifying the constraint name, which is a combination of the table name, the column in the constraint, and a "_foreign" suffix in a migration file. To drop the foreign key constraint, create a migration file and use the following:
public function up(): void
{
Schema::table('books', function (Blueprint $table) {
$table->dropForeign('fund_user_id_foreign');
});
}
To toggle (enable or disable) foreign key constraints in migrations, use these methods enableForeignKeyConstraints()
, disableForeignKeyConstraints()
, withoutForeignKeyConstraints()
:
Schema::enableForeignKeyConstraints();
Schema::disableForeignKeyConstraints();
Schema::withoutForeignKeyConstraints(function () {
// Constraints disabled within this closure...
});
How to remove migrations
While there's no Artisan command specifically for removing migrations, you can achieve this by rolling back executed migrations, thereby reversing their impact on the database schema. To check if migrations have been executed, use the command:
php artisan migrate:status
After running the command, you'll see the following in your terminal:
If you haven't executed php artisan migrate, you can just delete the migration file by right-clicking and selecting delete:
However, if the migration has been executed, you can roll back a specific migration using php artisan migrate:rollback with the batch number. To remove the last migration, use the command:
php artisan migrate:rollback --step=1
The -step option specifies the migrations to rollback; for example, -step=2 rolls back the last two migrations. After executing the command, the last migration is rolled back:
Then, delete the migration file. If using SQLite, clear the database.sqlite file before rerunning the migrate command to avoid errors. Alternatively, drop all tables to remove all migrations at once.
How to squash migrations
As your application scales, it may require additional migrations, leading to numerous files in the database/migrations directory. You can use the schema:dump command to squash these files into a single SQL file by running this command:
php artisan schema:dump
When you run this command, Laravel generates a "schema" file in your app's database/schema directory:
The schema file contains SQL statements. When migrating your database for the first time in Laravel, it runs the SQL statements in the schema file first and then proceeds with the remaining migrations not included in the schema dump.
How to use migration events
Laravel provides dispatch migration events for post-migration actions, such as notifying the admin via email. Events include the following:
-
MigrationsStarted - when a batch is about to execute.
-
MigrationStarted - when a single migration is about to execute.
-
MigrationsEnded - when a batch execution has ended.
-
MigrationEnded - when a single migration execution has ended.
These events extend the Illuminate\Database\Events\MigrationEvent class by default. You can register a listener (a class that responds to specific events within your application) in the app\Providers\EventServiceProvider directory for these events. Register the MigrationsStarted event by importing it in your EventServiceProvider.php file:
use Illuminate\Database\Events\MigrationsStarted;
Create a listener class to handle the event by running the following command:
php artisan make:listener MigrationStartedListener
This command creates a Listeners/MigrationStartedListener directory and class and handle()
function for handling migration events. Add the following code under the handle()
function to echo "Butters migration has started" before executing a migration batch:
public function handle(object $event): void
{
echo "Butters migration has started";
}
Next, go back to EventServiceProvider.php and import this class:
use App\Listeners\MigrationStartedListener;
To map the listener to the event, include the following in your protected $listen array:
MigrationsStarted::class => [
MigrationStartedListener::class
]
Run your migration to see the echoed message in your terminal:
Final thoughts
You've come a long way, congrats!
In this article, you have learned how to use migrations to improve your Laravel applications. The source code for migration files, including the highlighted examples, is available on GitHub. You can also find more information about Laravel migrations in the official documentation. Now you can see that migrations are necessary to monitor database changes, which aid in error detection and rollbacks in case of an error.
If you'd like to see the power of Laravel combined with ButterCMS check our tutorials: How to Build a Blog with Laravel (& Send Slack Notifications) & How to Build Compelling Laravel Landing Pages with ButterCMS
ButterCMS is the #1 rated Headless CMS
Related articles
Don’t miss a single post
Get our latest articles, stay updated!
Funke is a tech lover with a keen interest in building and promoting sustainable tech communities, especially among women and young adults. She is a backend developer, who is passionate about communal growth and has published articles on many blogs including her personal blog.