A factory is a recipe for building fake models in one line, and a seeder uses those recipes to fill your database with realistic data.
๐ชA factory is a cookie cutter: one shape, as many cookies as you like, and you can add sprinkles (states) when needed. A seeder is the baker who fills the display case each morning before the shop opens.
Data on demand
Short, readable tests
Post::factory()->create() replaces ten lines of setup, so tests show only what matters.
Realistic local apps
Develop against 50 users and 300 posts instead of an empty screen.
Same data for the team
Anyone can rebuild an identical development database with one command.
How the pieces fit
database/ โโโ factories/ โ โโโ UserFactory.php โ ships with Laravel โ โโโ PostFactory.php โโโ migrations/ โโโ seeders/ โโโ DatabaseSeeder.php โ entry point for db:seed โโโ PostSeeder.php
Fake a whole blog
-
Define a factory
Generate a factory for the model. In
definition(), return default values.fake()gives you Faker, a library of realistic random data.php artisan make:factory PostFactory --model=Post<?php namespace Database\Factories; use App\Models\User; use Illuminate\Database\Eloquent\Factories\Factory; class PostFactory extends Factory { public function definition(): array { return [ 'user_id' => User::factory(), 'title' => fake()->sentence(), 'body' => fake()->paragraphs(4, true), 'published_at' => now(), ]; } }The model needs the
HasFactorytrait (models made withmake:modelalready have it).'user_id' => User::factory()creates an author automatically when none is given. -
Use it: make, create and count
make()builds models in memory only;create()also saves them. Override any field by passing an array.$post = Post::factory()->make(); // not saved $post = Post::factory()->create(); // saved + author created $posts = Post::factory()->count(10)->create(); // 10 posts $post = Post::factory()->create(['title' => 'Pinned']); // override a field -
Add states for variations
A state is a named tweak to the defaults, like a draft post. It keeps tests readable:
Post::factory()->draft()says exactly what you mean.public function draft(): static { return $this->state(fn (array $attributes) => [ 'published_at' => null, ]); }test('drafts are hidden from the blog index', function () { Post::factory()->draft()->create(['title' => 'Secret draft']); $this->get('/posts')->assertDontSee('Secret draft'); }); -
Build relationships with
hasandforFactories understand your Eloquent relationships. Use
has()to create children andfor()to attach a parent.// A user with 3 posts, each with 5 comments $user = User::factory() ->has(Post::factory()->count(3)->has(Comment::factory()->count(5))) ->create(); // Magic shortcut for the same "has many" idea $user = User::factory()->hasPosts(3)->create(); // A post that belongs to a specific user and category $post = Post::factory() ->for($user) ->for(Category::factory()->state(['name' => 'Laravel'])) ->create(); -
Write seeders and wire up
DatabaseSeederA seeder is a class with a
run()method that creates data.DatabaseSeederis the entry point; it calls the others in order.php artisan make:seeder PostSeederclass DatabaseSeeder extends Seeder { public function run(): void { User::factory()->create([ 'name' => 'Test User', 'email' => 'test@example.com', // a known login for local dev ]); $this->call([CategorySeeder::class, PostSeeder::class]); } }public function run(): void { User::factory(10) ->has(Post::factory()->count(5)->for(Category::inRandomOrder()->first())) ->create(); } -
Run the seeders
db:seedrunsDatabaseSeeder.migrate:fresh --seeddrops every table, re-runs all migrations, then seeds: a perfect reset button for local development.php artisan db:seed # run DatabaseSeeder php artisan db:seed --class=PostSeeder # run just one seeder php artisan migrate:fresh --seed # wipe, rebuild, seedโ ๏ธ Never run migrate:fresh in productionIt deletes every table and all data. Laravel asks for confirmation in production, but treat it as a local-only command.
In tests, call
$this->seed()(or$this->seed(CategorySeeder::class)) when you need reference data like categories.
Avoid this, do that
$user = User::create([
'name' => 'A', 'email' => 'a@a.com',
'password' => bcrypt('x'),
]);
$post = Post::create([...]);
Hand-built setup is long, repetitive and breaks whenever a required column is added.
$post = Post::factory()
->for(User::factory())
->create();
Factories fill in sensible defaults, so your test only states what's special.
'published_at' => rand(0, 1) ? now() : null,
Random defaults make tests pass sometimes and fail other times ("flaky" tests).
'published_at' => now(),
// and a draft() state for null
Keep defaults predictable and express variations as named states.
Run php artisan tinker and type Post::factory()->count(3)->make() to preview what your factory produces without saving anything.
- Create
CategoryFactoryandCommentFactoryfor the blog. - Add a
draft()state and apopular()state (e.g.viewsover 1000) toPostFactory. - Seed 5 categories, 10 users and 50 posts, each with 0โ5 comments.
- Run
php artisan migrate:fresh --seedand browse your filled-up blog. - Rewrite one feature test from the previous lesson to use
has()andfor().
Key takeaways
- Factories define default fake attributes in
definition()usingfake(). make()builds in memory,create()saves;count(n)makes many.- States (
->draft()) name common variations; keep defaults predictable. has()creates children andfor()attaches a parent model.- Seeders fill the DB;
DatabaseSeedercalls them; run withdb:seedormigrate:fresh --seed.