Hey everyone
Recently after some research I came up with a question: how to handle default / initial data population in a Laravel project: should it go into seeders or migrations? I’ve seen two very different schools of thought.
What other devs say
Nuno Maduro says:
Seeders should only be used for environment-specific data (local or test), and not for default system values. Database seeders become outdated over time and may no longer reflect the true state of your production data. This can introduce inconsistencies if you need to re-run migrations to deploy your app elsewhere. In contrast, migrations are inherently reliable because they define the exact structure and transformations that shaped your production data, guaranteeing consistency across environments.
YouTube video in which nuno talks about seeders
Another developer explained why he always uses migrations for default values:
-
They support dependencies between tables.
-
Easier to revert a release by using down() to remove the inserted data.
-
Avoids the risk of someone forgetting to run the seeders, or accidentally running test seeders in production.
On the other hand, many developers say:
“Migrations should migrate schema, not seed data.”
But they often don’t address the fact that default values are actually part of the schema of the application.
My case
In my project I have two different types of default data:
-
Roles (user, admin): These feel like critical system data, so I’m leaning toward adding them in a migration.
-
DishCategories & DishSubcategories: Here it gets tricky. I have a large predefined array of categories and subcategories (kind of like a starter set). To avoid bloating the migration file, I moved that array into a separate PHP file and include it when inserting the data.
Now I’m wondering: is this an acceptable approach?
The important note
On the one hand, these are not “test” values, they are part of the actual application logic (used in search). On the other hand, they can evolve later by being managed in the admin panel, so maybe seeders make more sense here.
Automated testing concerns
Laravel Daily also mentioned a possible issue when putting data population inside migrations:
-
During automated tests, when migrations are re-run, the default data might get inserted multiple times unless you use insertOrIgnore or similar safeguards.
-
This could lead to duplicate rows in test environments.
YouTube video in which Laravel Daily talks about those 2 approaches
A code example of these 2 approaches (Seeder / Migration)
Seeder
class RolesSeeder extends Seeder
{
public function run(): void
{
Role::create(['name' => 'user']);
Role::create(['name' => 'admin']);
}
}
Migration
public function up(): void
{
Schema::create('roles', function (Blueprint $table) {
$table->id();
$table->string('name')->unique();
$table->timestamps();
});
$roles = [
'user',
'admin'
];
foreach ($roles as $role){
DB::table('roles')->insert([
'name' => $role,
'created_at' => now(),
'updated_at' => now(),
]);
}
}
Would be grateful to see your thoughts about seeding poroduction data and see the proper practicies
Best regards