Database seeders — DatabaseSeeder, calling sub-seeders
Concept
Database seeders are PHP classes that populate your database with initial or test data in a repeatable, version-controlled way. Every Laravel project ships with database/seeders/DatabaseSeeder.php as the root seeder — the entry point Artisan calls when you run php artisan db:seed. This class extends Illuminate\Database\Seeder and uses its call() method to delegate to sub-seeders, letting you compose a full dataset from focused, single-responsibility classes.
The call() method on Illuminate\Database\Seeder accepts a class name or an array of class names and dispatches them sequentially. Under the hood it resolves each class through the service container, so sub-seeders can type-hint dependencies in their constructors. Laravel also provides a callOnce() variant that checks a seeder_runs table and skips classes that have already executed, which is useful for idempotent production seeds that should only run once.
The $this->command property gives every seeder access to the Artisan console instance, so you can emit output with $this->command->info() or $this->command->getOutput()->progressStart(). This makes long-running seeds observable and debuggable from the terminal without adding ad-hoc echo statements.
A common architecture separates "reference data" seeders (countries, currencies, permission names — data that belongs in every environment) from "development data" seeders (fake users, sample posts — data you only want locally). Wrap development seeders in an app()->environment('local', 'staging') guard inside DatabaseSeeder::run(), or use a separate DevSeeder class invoked with php artisan db:seed --class=DevSeeder.
| Artisan Command | Effect |
|---|---|
php artisan db:seed | Runs DatabaseSeeder |
php artisan db:seed --class=UserSeeder | Runs a specific seeder |
php artisan migrate:fresh --seed | Drops all tables, re-runs migrations, then seeds |
php artisan migrate:fresh --seeder=DevSeeder | Same but uses a custom root seeder |
Code Example
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
// Reference data — safe for all environments
$this->call([
CountrySeeder::class,
CurrencySeeder::class,
PermissionSeeder::class,
]);
// Development data — local and staging only
if (app()->environment('local', 'staging')) {
$this->call([
UserSeeder::class,
PostSeeder::class,
]);
}
}
}
// ---
class UserSeeder extends Seeder
{
public function run(): void
{
$this->command->info('Seeding users...');
\App\Models\User::factory()
->count(50)
->create();
// Create one known admin account for development login
\App\Models\User::factory()->create([
'email' => 'admin@example.com',
'name' => 'Admin User',
]);
$this->command->info('Done seeding users.');
}
}
// ---
class PermissionSeeder extends Seeder
{
public function run(): void
{
$permissions = [
'posts.create',
'posts.edit',
'posts.delete',
'users.manage',
];
foreach ($permissions as $permission) {
\App\Models\Permission::firstOrCreate(['name' => $permission]);
}
}
}Interview Q&A
Q: What is the difference between db:seed and migrate:fresh --seed, and when would you use each?
db:seed runs seeders against the current database without touching its schema — useful when you only want to repopulate data without destroying existing structure. migrate:fresh --seed drops every table, re-runs all migrations from scratch, then seeds — it gives you a clean slate and is the standard command in CI pipelines and during early development when the schema is still changing. In production you would almost never run migrate:fresh; instead you run migrate to apply incremental changes and then possibly a targeted seeder for new reference data.
Q: How do you prevent a seeder from running multiple times in production?
Use callOnce() instead of call() for idempotent reference-data seeders. callOnce() records the class name in a seeder_runs database table after the first successful run and skips it on subsequent invocations. For seeders that do not support callOnce(), you can guard individual inserts with firstOrCreate() or insertOrIgnore() so re-running them is a no-op. Never use truncate() in a seeder that could execute against a production database.
Q: How can sub-seeders receive service-container dependencies?
Because Illuminate\Database\Seeder::call() resolves seeder classes through the application container, you can add constructor parameters type-hinted to any bound interface or concrete class and Laravel will inject them automatically. For example, a CurrencySeeder could inject a CurrencyApiClient to fetch live ISO currency data during seeding. The same resolution applies when you call a seeder programmatically in a test via $this->seed(CurrencySeeder::class).