
August 12, 2026
12 min read
By Kokil Thapa | Last reviewed: September 2026
Choosing between Laravel seeders vs factories when to use each tool is one of those small architectural decisions that compounds across every environment you maintain. Seeders insert deliberate, often fixed records—roles, tax rates, demo categories—while factories generate plausible fake data on demand for tests and local development. On production Laravel applications I have shipped since 2010, the teams that confuse the two end up with brittle tests, bloated staging databases, and seed scripts that break every time a column changes. This guide walks through the decision with Laravel 13.x patterns, real file paths, and the combination that keeps database migrations and seeding predictable from local through CI to production.
What is the difference between Laravel seeders and factories?
Both tools populate your database, but they answer different questions. A seeder is an orchestration class—typically under database/seeders/—that runs explicit inserts, often via Eloquent models or DB::table(), to put known rows into tables. A factory is a definition class under database/factories/ that describes how to build one model instance with randomized but realistic attributes using Faker.
In Laravel 13 (PHP 8.3 minimum), the wiring is standard. You register seeders in DatabaseSeeder and invoke factories from tests with User::factory()->create() or from seeders with User::factory()->count(50)->create(). The framework ships both features in the core testing and seeding stack documented at laravel.com/docs/13.x/seeding and laravel.com/docs/13.x/eloquent-factories.
Seeders in one sentence
Seeders are scripts you run deliberately—php artisan db:seed or as part of deployment—to populate baseline data. They belong in version control because the data they insert is part of your application contract: default admin role, payment status enums, Nepal-specific lookup values if your app uses them.
Factories in one sentence
Factories are blueprints for creating model instances. They shine in PHPUnit or Pest tests where you need an Order with a related User but do not care whether the email is jane@example.com or bob@example.com. For deeper factory patterns, see the companion guide on Laravel model factories for realistic test data.
| Criteria | Seeders | Factories |
|---|---|---|
| Primary purpose | Load fixed reference or demo datasets | Generate fake records on demand |
| Typical trigger | php artisan db:seed, deploy hook, CI setup | Model::factory()->create() in tests |
| Data repeatability | Identical on every run (when written correctly) | Varies per run unless you pin states |
| Production use | Yes—for lookup tables, initial config rows | No direct production writes; definitions only |
| Coupling to Faker | Optional | Built-in via fake() helper |
| Best paired with | firstOrCreate, idempotent checks | Factory states, relationships, afterCreating |
When should you use Laravel seeders instead of factories?
Reach for a seeder whenever the row itself matters—not just that a row exists. If removing or randomizing the data would break a feature, it belongs in a seeder, not a factory call scattered across tests.
Concrete cases I use seeders for on client projects and legal-tech portals:
- Reference data — user roles, permission names, document types, court fee categories, order statuses. These rows have stable IDs or slugs your code references.
- Environment bootstrap — a default admin account on staging, demo law-firm categories on a QA server, currency codes for an eCommerce catalog.
- Idempotent production inserts — VAT rate rows, shipping zones, feature flags stored in the database rather than config files.
- Cross-table baseline sets — when five tables must be populated together in a fixed relationship before the app boots cleanly.
Example: idempotent role seeder (Laravel 13)
// database/seeders/RoleSeeder.php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
class RoleSeeder extends Seeder
{
public function run(): void
{
foreach (['admin', 'staff', 'client'] as $role) {
Role::firstOrCreate(['name' => $role, 'guard_name' => 'web']);
}
}
}
Notice firstOrCreate. Production seeders must be safe to re-run after deploy. Blind insert() calls fail on the second run when unique constraints fire—a pattern I have debugged on more than one client portal with role-based access.
When seeders beat factories
- The application code reads specific slug or ID values.
- QA needs a predictable demo login every sprint.
- Staging must mirror production structure without 10,000 fake users.
- Compliance or audit requires known sample records—not random PII.
For enterprise apps with strict data boundaries, pairing seeders with enterprise application development practices keeps staging closer to production shape without importing real customer data.
When should you use Laravel factories instead of seeders?
Factories win when you need shape without caring about content. Automated tests are the clearest case: each test should create only the models it needs, in isolation, without depending on a global seed state that another test might mutate.
Use factories when:
- Writing feature or unit tests that need users, orders, bookings, or documents.
- Prototyping UI locally with 200 products—not 200 hand-written arrays.
- Stress-testing queries with large datasets in a disposable database.
- Defining reusable "states" like
->unverified()or->withMedia()that tests opt into.
Example: factory with relationships
// database/factories/BookingFactory.php
public function definition(): array
{
return [
'user_id' => User::factory(),
'starts_at' => fake()->dateTimeBetween('+1 day', '+2 weeks'),
'status' => 'pending',
];
}
public function confirmed(): static
{
return $this->state(fn () => ['status' => 'confirmed']);
}
In a Pest or PHPUnit test:
it('allows confirmed bookings to be cancelled', function () {
$booking = Booking::factory()->confirmed()->create();
$this->actingAs($booking->user)
->delete(route('bookings.destroy', $booking))
->assertRedirect();
});
This test does not call db:seed. It owns its data. That isolation is why factories belong in your testing and optimization workflow, and why I avoid seeding entire databases before every test suite—a common mistake that slows CI from minutes to tens of minutes on larger apps.
Factory features worth learning in 2026
Laravel 13 factories support afterCreating callbacks for attaching media, creating pivot rows, or firing domain events. On a production booking app like Adventure Third Pole Trek, factories generated supplier links and itinerary lines in tests without duplicating that logic in seeders. Relationship shortcuts—User::factory()->hasPosts(3)—keep test setup readable.
External reference: the PHPUnit data provider docs at docs.phpunit.de complement factory usage when you parameterize edge cases across many synthetic records.
How do you combine Laravel seeders and factories in production apps?
The mature pattern is not seeders versus factories—it is seeders calling factories where bulk volume helps, plus factories standing alone in tests. Your DatabaseSeeder orchestrates specialized seeders; only some of them delegate to factories.
Recommended project layout
database/
├── factories/
│ ├── UserFactory.php
│ └── OrderFactory.php
├── seeders/
│ ├── DatabaseSeeder.php
│ ├── RoleSeeder.php // fixed — no factory
│ ├── SettingsSeeder.php // fixed — no factory
│ └── DemoCatalogSeeder.php // calls Product::factory()->count(100)
└── migrations/
DatabaseSeeder orchestration
// database/seeders/DatabaseSeeder.php
public function run(): void
{
$this->call([
RoleSeeder::class,
SettingsSeeder::class,
]);
if (app()->environment('local', 'staging')) {
$this->call(DemoCatalogSeeder::class);
}
}
Production runs only the fixed seeders. Local and staging get demo volume via factories inside a guarded seeder. Never ship User::factory()->count(500)->create() unguarded in DatabaseSeeder—I have seen that inflate MySQL storage on a shared staging server and mask real performance issues.
Real-world split on an eCommerce Laravel app
On a platform like Nepal Gift Card, seeders hold payment method enums, gift-card denomination rows, and admin roles—data the checkout code expects. Factories generate thousands of synthetic gift-card orders in tests to verify idempotent redemption and queue jobs. The same OrderFactory never runs in production seeding; only the enum seeder does.
Align this with modern Laravel architecture: keep domain rules in services and actions, use seeders for bootstrap data, and use factories to exercise those rules under varied inputs. If you store JSON config blobs during development, validate structure with the JSON formatter before committing seeder payloads.
How do Laravel seeders and factories fit into CI/CD and testing?
Your pipeline should treat migrations, seeders, and factories as three separate concerns with different entry points.
CI/CD commands that actually work
# .gitlab-ci.yml excerpt — test job
script:
- cp .env.testing .env
- php artisan migrate --force
- php artisan db:seed --class=RoleSeeder --force
- php artisan test --parallel
Seed only what tests assume globally—usually minimal reference data. Let each test factory-create the rest. On GitLab CI pipelines I maintain with Deployer 7, production deploy runs migrate --force plus a small idempotent seeder class, never full demo seeding. See the step-by-step CI/CD pipeline with GitLab CI for Laravel for hook placement after symlink swap and PHP-FPM reload.
Anti-patterns to avoid
- RefreshDatabase + full DatabaseSeeder — doubles migration time and hides test coupling.
- Hard-coded user ID 1 in seeders that tests rely on—use role slugs or factory-created actors instead.
- Duplicating factory logic inside seeders as raw arrays—call the factory or extract a shared static method.
- Seeding PII-like fake emails to production — even Faker output does not belong on live MySQL 9.7 unless the environment is disposable.
Database choice matters at scale: whether you run MySQL 8.4 LTS or PostgreSQL 18, bulk factory inserts in tests stress different indexes. The PostgreSQL guide for Laravel developers covers transaction wrapping around test databases—useful when factories create deep graphs.
Filament, APIs, and admin panels
Admin panels often need a bootstrap admin seeder once; everything else is operational data entered through the UI. If you use Laravel Filament, seed the super-admin role and permissions, then let staff create records live. API test suites should factory-create tokens via Sanctum rather than seeding OAuth clients globally—patterns covered in Laravel API best practices and policies and gates.
For Nepali businesses standardizing on Laravel, this split reduces support load after launch—predictable seeders mean fewer "works on my machine" calls during Dashain freeze periods when clients hesitate to deploy. That operational angle is why Laravel fits Nepali business constraints when tooling stays boring and explicit.
Key Takeaways
- Use seeders for fixed reference, lookup, and bootstrap data your code depends on by slug, ID, or known value.
- Use factories for tests and local bulk data where random plausible values are acceptable and isolation matters.
- Combine them: production seeders stay idempotent; demo seeders call factories behind
app()->environment()guards. - Never run full
DatabaseSeederbefore every test—seed minimally in CI, factory-create per test case. - Keep factory definitions as the single source of fake row shape; avoid copy-pasting the same arrays into seeders.
- Document which seeders run on production deploy versus staging so ops and support and maintenance teams know what to expect.
People Also Ask
Can Laravel factories replace seeders entirely?
No. Factories generate variable data and are optimized for object creation in tests. Seeders express intentional datasets your application expects—default roles, settings, tax tables. Replacing seeders with factories in production would produce different rows on every deploy and break code that references stable slugs or IDs.
Should you run db:seed in production?
Run only small, idempotent seeders in production—typically after migrations during deploy. Use firstOrCreate or equivalent upsert logic. Avoid demo seeders and bulk factory creation. On Ubuntu servers running PHP 8.5 and Laravel 13, wire the seeder into your Deployer task or CI deploy step, not into artisan schedule.
What is the difference between DatabaseSeeder and individual seeders?
DatabaseSeeder is the entry point that calls other seeder classes in order. Individual seeders each own one concern—roles, settings, demo catalog. Splitting them keeps production deploys fast and lets CI invoke only RoleSeeder without dragging in demo data.
How do seeders work with Laravel model events and observers?
Both seeders and factories trigger Eloquent events unless you use Model::withoutEvents() or createQuietly(). If observers send emails or hit external APIs, seeding can cause side effects. Wrap noisy seeders in withoutEvents or disable specific observers during db:seed on staging.
Ship predictable data layers from day one
Getting Laravel seeders vs factories when to use right is cheap insurance: tests stay fast, staging stays readable, and production deploys stop failing on duplicate key errors. Seed the contract; factory the chaos. If you are planning a new Laravel 13 application—or untangling a legacy app where seeders and factories were mixed without plan—custom software development support can audit your migration and seeding pipeline before bad patterns reach production. For regex-heavy factory definitions or Faker edge cases, the regex tester on this site helps validate patterns offline. When you are ready to talk through architecture for your next project, contact us with your stack and deployment setup.
Frequently Asked Questions
0 Comments
Leave a comment
Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

