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