Understanding Database Migrations: A Laravel Beginner's Guide
Learn how to manage your database schema using Laravel migrations. Discover how to create, define, and run schema updates for your Task Manager project.
Previously in this course, we built the UI for our Task Manager in Task Manager: Building the User Interface. Up until now, our "tasks" have been hardcoded arrays in our controller. To build a real application, we need a persistent way to store data.
In this lesson, we’ll move away from static data and start using migrations. Think of migrations as "version control for your database." Instead of manually running SQL queries in a database tool, we write PHP code that describes our table structure, allowing us to share schema changes across teams and environments reliably.
Why Migrations Matter
If you’ve ever worked on a project where you had to manually run ALTER TABLE commands, you know the pain of "it works on my machine but not on production." Migrations solve this by providing a programmatic way to define your database structure. Because these files live in your repository, everyone on your team (and your production server) stays in sync.
Creating Your First Migration
To start, we need a table for our tasks. Laravel provides an Artisan command to generate a migration file. Open your terminal in your project root and run:
Bashphp artisan make:migration create_tasks_table
Laravel will generate a new file in the database/migrations directory. If you look at the filename, you’ll see it’s prefixed with a timestamp—this ensures Laravel executes them in the correct chronological order.
Defining Columns with the Schema Builder
Open the newly created file. You will see two main methods: up() and down(). The up() method is where we define the changes (creating tables, adding columns), and the down() method is where we reverse them (dropping tables, removing columns).
Inside the up() method, use the Schema facade to define your columns:
PHPuse 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('tasks', function (Blueprint $table) { $table->id(); #6A9955">// Auto-incrementing primary key $table->string('title'); #6A9955">// A VARCHAR column $table->text('description')->nullable(); #6A9955">// An optional TEXT column $table->boolean('is_completed')->default(false); #6A9955">// A boolean with a default $table->timestamps(); #6A9955">// Creates created_at and updated_at columns }); } public function down(): void { Schema::dropIfExists('tasks'); } };
This code is expressive and readable. We are using the Blueprint object to fluently define our columns. Whether you are using SQLite (which we configured in Setting Up the Local Development Environment) or MySQL, the syntax remains the same.
Running Migrations
Defining the file isn't enough; we need to apply it to the database. Run the following command:
Bashphp artisan migrate
Laravel will look at the migrations table in your database to see which files have already been run. It then executes the up() method of any new files it finds. If you ever need to revert a change, you can use php artisan migrate:rollback.
Hands-on Exercise
- Generate a migration for a
categoriestable usingphp artisan make:migration create_categories_table. - Add a
namestring column and aslugstring column to theup()method. - Run
php artisan migrateto apply the change. - Verify the new table exists using your database client or
php artisan tinker.
Common Pitfalls
- Forgetting the
down()method: Always define how to undo your changes. If you ever need to rollback, you don't want the process to fail because thedown()method is empty. - Modifying existing migrations: Once a migration has been pushed to a shared repository, never change it. If you need to add a column later, create a new migration file instead.
- Ignoring the timestamp: If you manually rename files or edit timestamps, you might break the order in which migrations run. Stick to the generated names.
Mastering migrations and seeders in Laravel for beginners is a rite of passage for any developer. By treating your database schema as code, you ensure your application remains stable as it evolves. If you're interested in advanced techniques, you might eventually explore Laravel online schema change for zero-downtime deployments.
Up next: We will bridge the gap between our database tables and our code by creating an Eloquent model to interact with our new tasks table.
Work with me

FilamentPHP Admin Panel & Dashboard Development
A powerful admin panel for your Laravel app — built with FilamentPHP so you can manage everything without touching the database.

Laravel REST API Development
Clean, secure, well-documented Laravel REST APIs — the backend engine for your app, mobile client, or SaaS. Built by an API specialist.