Laravel seed data
Laravel + PostgreSQL test data beyond DatabaseSeeder and factories — distributions and FKs from the schema itself.
Laravel projects seed through database/seeders/DatabaseSeeder.php, run by php artisan db:seed — usually model factories plus a loop of Model::create() calls. It works until relations and constraints outgrow the seeder. This guide shows the Laravel-native seeding workflow, where it breaks, and the schema-driven alternative.
How Laravel seeding normally works
Laravel owns the schema through migrations (php artisan migrate). Seeding is a seeder class, run with php artisan db:seed (or migrate:fresh --seed):
// database/seeders/DatabaseSeeder.php
namespace Database\Seeders;
use App\Models\User;
use Illuminate\Database\Seeder;
class DatabaseSeeder extends Seeder
{
public function run(): void
{
for ($i = 0; $i < 50; $i++) {
User::create([
'email' => fake()->unique()->safeEmail(),
'name' => fake()->name(),
]);
}
}
}Works for a standalone model. The moment Order has a belongsTo relation, the seeder has to create the user first and attach it — dependency order becomes something the seeder encodes by hand.
Where Laravel seed scripts break
- Foreign keys.
Order::create(['user_id' => ...])needs a persisted parent id. The seeder grows ordering logic and lookups, and every added relation is another line of bookkeeping. Miss one and you get a foreign-key violation or an orphaned row. - Schema drift. A new non-null column, a cast, a validation rule — each one is a seeder change. The migration and model move;
DatabaseSeederdoes not, and tests start failing for reasons nobody can explain. - Uniform randomness.
fake()produces plausible values, not production distributions. If your realstatuscolumn is 70%active, a flat loop gives you roughly a third of each — and query plans in tests stop matching production.
Schema-driven seeding with Weavori
Weavori reads the database schema — the one Laravel migrated — and generates rows from it. The Laravel-specific workflow:
Weavori introspects tables, foreign keys, types, and constraints, resolves dependency order (parents before children), and writes via COPY. Every generated order.user_id references a real generated user — by construction, not by seeder ordering.
The schema is the seed
When a Laravel migration changes the schema, the generated data changes with it. There is no seeder to update because nothing in it duplicated the schema.
Laravel + Weavori in CI
The loop that keeps test databases honest:
Weavori supports API keys for headless auth, standardized exit codes, and offline license validation — CI runners on restricted networks don't phone home. Full GitHub Actions / GitLab / CircleCI / Jenkins examples in the CI/CD guide.
Which Laravel seed command do you need?
| Goal | Command |
|---|---|
Run DatabaseSeeder against an existing database | php artisan db:seed |
| Migrate, then seed | php artisan migrate --seed |
| Drop every table, re-migrate, then seed | php artisan migrate:fresh --seed |
| Run a single seeder class | php artisan db:seed --class=UserSeeder |
| Generate a production-shaped dataset with no seeder | npx --yes @weavori/cli generate postgres://localhost:5432/mydb |
php artisan db:seed
php artisan db:seed runs Database\Seeders\DatabaseSeeder, which calls the seeder classes it lists. Artisan supplies the runner and Eloquent supplies the models; the rows come from the seeder, so the seeder is where relation bookkeeping accumulates as belongsTo and hasMany relationships appear.
php artisan migrate --seed
migrate --seed applies pending migrations and then runs the seeder in one command — the standard deploy-time pair. Seeding after migration is not optional: the seeder writes into the columns the migration just created.
php artisan migrate:fresh --seed
migrate:fresh drops every table, re-runs all migrations from scratch, and with --seed repopulates in the same pass. It is the clean-slate command — and the one that exposes a seeder which only works against a database somebody has already filled by hand. migrate:refresh --seed is the gentler variant: it rolls migrations back instead of dropping the tables.
php artisan db:seed --class=UserSeeder
--class runs one seeder instead of the default. It is useful while iterating on a single table, and a reminder that Laravel expects seeding to be split into classes per concern — more files, each holding its own copy of what the schema already declares.
When to keep your Laravel seeder
Honesty about boundaries: keep DatabaseSeeder for deterministic unit-test fixtures — a known user the test asserts against. Keep model factories for single values inside tests. Weavori replaces the dataset problem: filling dev, staging, and CI databases with production-shaped, FK-intact rows.
Related guides
- PostgreSQL data generator — what a Postgres data generator does, and how it relates to fake and dummy data
- Postgres test data — the five ways to get test data, compared
- Postgres seed data generator — the tool comparison
- Postgres synthetic data — synthetic vs. mock vs. anonymized
- Rails seed data and Django seed data — the same loop in other stacks
- Quickstart — first seed in minutes