laragear/populate
Populate makes it easy to fill Laravel databases with realistic fake data using dedicated populators. It understands model relationships, so you can generate consistent records for demos, tests, and local development without writing tedious seeders or manual fixtures.
Install the package with Composer and let Laravel's package discovery wire it up:
composer require laragear/populate --dev
Then publish the config so you can see what the package expects:
php artisan vendor:publish --tag=populate.config
Where to look first:
config/populate.php — the list of registered populators (the package's equivalent of seeders), plus any connection/behaviour defaults.php artisan populate --help — the authoritative list of flags for your installed version; the signature is the fastest way to learn what the command supports (--class, --database, --seed, etc.).vendor/laragear/populate/src/ — the Populator base class and the underlying command. Both are small and readable; reading them beats guessing from the README.First use case: write one populator that wraps an existing factory and run it against a scratch database.
namespace Database\Populators;
use Laragear\Populate\Populator;
use App\Models\User;
class UserPopulator extends Populator
{
public function populate(): void
{
User::factory()->count(50)->create();
}
}
Register it in config/populate.php, then:
php artisan migrate:fresh && php artisan populate
That is the whole loop: factories do the work, populators decide the order. If you already have model factories, you are 90% done on day one — Populate replaces hand-rolled seeder classes rather than replacing factories.
Keep populators as thin ordering layers. A populator should rarely contain data definitions; it should call factories and coordinate counts. Anything reusable (attribute shapes, states, relationships) belongs in the factory, where php artisan make:factory and ->state() already give you composability.
Order is the dependency graph. Populators run sequentially, so declare them parent-first: users → posts → comments. For relations you don't want to hand-wire, use the factory relationship helpers:
Post::factory()
->count(20)
->for(User::factory()) // or ->recycle($users)
->create();
recycle() is usually better than for() for child rows — it reuses a small pool of already-persisted parents instead of creating one per child.
Separate local dev data from test data.
"scripts": { "db:demo": ["@php artisan migrate:fresh", "@php artisan populate"] }
RefreshDatabase + targeted factories. Reserve populators for the handful of tests that need a realistic fixture set (search indexes, reporting queries, pagination edge cases) — calling $this->artisan('populate') once in setUp is far cheaper than writing them by hand.Make it idempotent if you plan to run it against a database you don't drop first. Guard at the top of each populator:
if (User::query()->exists()) {
return;
}
Otherwise populate on a non-fresh DB will either duplicate rows or blow up on unique constraints, and you'll waste an afternoon on the error message rather than the cause.
Pass context between populators via the container rather than globals — resolve a shared value object (or app()->instance()) in the first populator and read it in later ones. This keeps cross-populator coupling explicit when, say, posts need to reference specific users created earlier.
CI and preview environments. Because populators are plain classes registered in config, they run fine in a pipeline. Pair them with a dedicated connection:
php artisan populate --database=demo
Keep them out of the production image by requiring the package with --dev and guarding the command in AppServiceProvider::boot() ($this->app->isProduction() → don't register).
v2.0.2 — command registration changed. This release removes the [AsCommand] attribute that "for some reason duplicated the command registration." Practical consequences:
php artisan list may have shown populate more than once, and command lookup could be ambiguous depending on how you invoked it. After upgrading, confirm you see exactly one entry.commands() in bootstrap/app.php on Laravel 11+). Check php artisan list after upgrading — a silently missing command is the failure mode here.Factory existence is a hard requirement. Populate composes factories; it does not replace them. Model::factory() failing with "factory not found" means you skipped make:factory or the model doesn't use HasFactory.
Large counts. ->count(100_000) in one call will spike memory and can blow past max_execution_time in a container. Chunk it:
for ($i = 0; $i < 100; $i++) {
Order::factory()->count(1_000)->create();
}
Unique columns. unique() on a factory attribute draws from Faker's unique pool, which is per-process. Across multiple populators in one run you're fine; across repeated runs against the same DB you are not — use Sequence or a distinct prefix per run.
Config caching bites. Registered populators come from config. After adding a class, php artisan config:clear (or re-run config:cache) before debugging why it "isn't being called." This is the single most common false alarm with config-driven packages.
Foreign keys and truncation. migrate:fresh handles ordering; db:wipe plus manual truncation does not. If you see FK violations only in CI, suspect the cleanup path, not the populators.
Extension points: the Populator base class (subclass for shared setup/teardown), the config file (registration, defaults), and service-container bindings (swap or decorate the command if you need custom flags). Prefer subclassing the base class over copying logic between populators — that's where the package's ergonomics actually pay off as your dataset grows.
How can I help you explore Laravel packages today?