Laravel Migrations Tutorial: Create Tables, Modify Columns, and More

January 5, 2024 · 5 min read

2026 UPDATE - LARAVEL 13
We’re excited to announce that we have updated all of our blog post examples to reflect the new Laravel 13 version! Our previous examples were based on Laravel 10, but with the release of Laravel 13, we wanted to ensure that our readers have access to the most up-to-date information and examples. Laravel 13 requires PHP 8.3 or higher and introduces zero breaking changes from Laravel 12.
Ensuring Data Consistency with Migrations
Setting up your database tables correctly is like laying the foundation for a building. For our Laravel app project, migrations are the blueprint that defines the structure of our data. Let’s walk through the process of creating a robust database schema for a blog with users, profiles, posts, tags, and images using Laravel’s migration system. Coding time!
Getting Started with Laravel Migrations
Migrations in Laravel are PHP files that describe database changes, such as creating or altering tables. They give you the power to evolve your database schema over time in a version-controlled manner. Whether you’re adding a new feature or modifying an existing one, migrations ensure that these changes are applied consistently across all environments.
The Blueprint of Our Blog
Let’s define the main features of our blog:
Users: The authors who write blog posts.
Profiles: The profiles of the users.
Posts: The articles or blog entries published by users.
Tags: Keywords or topics that categorize posts.
Images: Photos or graphics associated with posts or users.
Before we create the migrations, let’s use the flag -mfsc for creating everything that is needed: model, migration, controller, seeder and factory.
Users
A new Laravel installation already has the User model and seeder. That’s why in this case we only need to create the seeder and the controller. Let’s start with the seeder:
php artisan make:seeder UserSeeder
Now we create the controller with the command:
php artisan make:controller UserController
Profiles
For the Profile model, you might handle profile creation and updates through the UserController. Thus, you wouldn’t necessarily need a dedicated controller. We will need the model, migration, factory and seeder. We can create all of them through the command:
php artisan make:model Profile -mfs
Posts
For the Post model, you typically want a controller to handle CRUD operations for blog posts. In this case, we need everything, that’s why we added the -mfsc flag. Let’s create a model, a migration, a factory, a seeder and a controller through the command:
php artisan make:model Post -mfsc
Tags
For the Tag model, you may not need a controller if tag management is done within another controller, like PostController. That’s why besides the model, we will create the migration, factory and seeder:
php artisan make:model Tag -mfs
Images
Similarly, for the Image model, if image uploads are managed through the PostController, you may not need a separate controller:
php artisan make:model Image -mfs
Each command will create the following:
A model class in the
app/Modelsdirectory.A migration file in the
database/migrationsdirectory.A factory file in the
database/factoriesdirectory.A seeder file in the
database/seedersdirectory.
And for User and Post, where the -c flag is included, it will also create:
A controller file in the
app/Http/Controllersdirectory.
Modifying Our Migrations
Users Migration
Every blog needs authors, and in Laravel, we’ll start by setting up a users table. With the previous command, we already have a default migration file, and we will modify it to fit our blog application needs:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateUsersTable extends Migration
{
public function up()
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->string('email_verified_at')->nullable();
$table->string('password');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('users');
}
}Profiles Migration
We also have a profiles table that provides additional information about our users:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateProfilesTable extends Migration
{
public function up()
{
Schema::create('profiles', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->text('bio')->nullable();
$table->string('website')->nullable();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('profiles');
}
}Posts Migration
The posts table will hold all the blog entries written by our users:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePostsTable extends Migration
{
public function up()
{
Schema::create('posts', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('title');
$table->text('content');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('posts');
}
}Tags Migration
To help categorize posts, we’ll set up a tags table:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateTagsTable extends Migration
{
public function up()
{
Schema::create('tags', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('tags');
}
}Post_Tag Pivot Table Migration
Since a post can have multiple tags and vice versa, we’ll need a pivot table to establish a many-to-many relationship:php artisan make:migration create_post_tag_table --create=post_tag
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreatePostTagTable extends Migration
{
public function up()
{
Schema::create('post_tag', function (Blueprint $table) {
$table->foreignId('post_id')->constrained()->onDelete('cascade');
$table->foreignId('tag_id')->constrained()->onDelete('cascade');
$table->primary(['post_id', 'tag_id']); // Composite primary key
});
}
public function down()
{
Schema::dropIfExists('post_tag');
}
}Images Migration
Lastly, we’ll need a table to store images associated with our posts:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateImagesTable extends Migration
{
public function up()
{
Schema::create('images', function (Blueprint $table) {
$table->id();
$table->morphs('imageable');
$table->string('path'); // This will store the file path of the image
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('images');
}
}Running the Migrations
Once these migration files are created, run them with Laravel’s artisan command:
php artisan migrateThis command will execute each migration in order, creating your database schema as defined.
SQLite by default: Laravel 13 uses SQLite as the default database for new projects, so you can run php artisan migrate immediately after installation without any additional database configuration.
Squashing Migrations with schema:dump
As your project grows, you may accumulate dozens or hundreds of migration files. Laravel provides php artisan schema:dump to squash all existing migrations into a single SQL file stored in database/schema. On fresh installs, Laravel will load this SQL file first, then run any new migrations on top of it, keeping your migration history clean.
# Squash all migrations into a single SQL file
php artisan schema:dump
# Squash and delete the existing migration files
php artisan schema:dump --pruneThis is especially useful for large projects where running all migrations from scratch on every fresh deployment would be slow.

Conclusion
With these migrations, we’ve laid the groundwork for our blogging platform. Each table is designed to hold specific pieces of data, with relationships that reflect the real-world connections between users, their profiles, blog posts, tags, and images. Migrations provide a clear, version-controlled path to evolve this schema as our blog grows and changes, ensuring a solid foundation for our Laravel application.
Upcoming Articles in the Series
This article is part of our series Laravel for Beginners: A Step-by-Step Guide to Learn the Concepts.
Bring Your Ideas to Life 🚀
If you need help with a Laravel project let’s get in touch.
Lucky Media is proud to be recognized as a leading Laravel Development Agency
Technologies

Stay up-to-date
Be updated with all news, products and tips we share!
On this page
- 2026 UPDATE - LARAVEL 13
- Ensuring Data Consistency with Migrations
- Getting Started with Laravel Migrations
- Modifying Our Migrations
- Users Migration
- Profiles Migration
- Posts Migration
- Tags Migration
- Post_Tag Pivot Table Migration
- Images Migration
- Running the Migrations
- Conclusion
- Upcoming Articles in the Series
- Bring Your Ideas to Life 🚀

