Seed files are the script that loads known, predictable data into a database once its schema already exists — a default admin account, a list of countries, a handful of subscription plans. Every framework needs one, since an empty database is useless for development, demos, or tests. The confusion is not what a seed file does; it is where the line sits between seeding and migrating, and why seed scripts so often end up duplicating rows on every run.
What changed in 2026
- Seed scripts increasingly run against branched databases, not shared dev instances. Neon and PlanetScale branching lets every pull request seed its own throwaway copy, so idempotency matters less there but still matters for CI and local setup.
- AI-assisted scaffolding tools now generate seed files from a schema directly, inferring plausible values for each column. Useful for a first draft; still needs a human pass for realistic relationships and edge cases.
- Seed and fixture tooling converged. The line between "seed file" (dev/demo data) and "test fixture" (test data) has blurred, with the same factory functions increasingly feeding both paths.
What actually goes in a seed file
A seed file is a script — SQL, or a small program in your app's language — that inserts rows representing a known starting state. Good candidates: reference data your app cannot function without (countries, currencies, roles, plan tiers), and enough sample records (a demo user, a handful of orders) to make the UI usable without a real signup flow. Bad candidates: anything environment-specific, anything that should differ between developers, and anything sensitive.
A minimal example, written as plain SQL:
INSERT INTO roles (name) VALUES ('admin'), ('member')
ON CONFLICT (name) DO NOTHING;
INSERT INTO plans (name, price_cents) VALUES
('free', 0), ('pro', 2900)
ON CONFLICT (name) DO NOTHING;
The ON CONFLICT DO NOTHING clause is doing the real work here — without it, running this file twice inserts duplicate rows.
Seed file vs migration
These get conflated because they often live in neighboring folders. A migration changes the database's shape: add a column, create a table, add an index. A seed file changes its contents: insert rows into a shape that exists. Running migrations out of order corrupts your schema; running a seed file twice, if written well, should be a no-op. Treat a seed file as data, and a migration as structure, and most confusion disappears. Wrapping the insert statements in a database transaction keeps a failure partway through from leaving a half-seeded table.
Seeding conventions by framework
| Framework |
Seed file location |
Run command |
Idempotent by default |
| Ruby on Rails |
db/seeds.rb |
rails db:seed |
No — you write the guard clauses |
| Django |
fixtures (.json/.yaml) or a management command |
loaddata or custom command |
Yes, for loaddata; custom commands vary |
| Laravel |
database/seeders/ |
php artisan db:seed |
No — use firstOrCreate |
| Prisma |
prisma/seed.ts |
npx prisma db seed |
No — use upsert |
| Knex.js |
seeds/ directory |
knex seed:run |
No — typically truncates first |
None of these enforce idempotency for you — that responsibility sits with whoever writes the script, the single most common source of seed-related bugs.
Writing an idempotent seed script
Use upsert, ON CONFLICT, or firstOrCreate-style calls instead of plain inserts. Key each row on something stable — a slug, an email, a well-known UUID — not an auto-incrementing ID that shifts between runs. If a seed script must run destructive setup, gate it firmly behind an environment check so it can never fire against a database with real data. A trigger defined on the target table can also fire unexpectedly during a bulk insert, so check what a database trigger does before seeding a table for the first time.
Common mistakes
No conflict handling. Plain INSERT statements re-run on every deploy, silently duplicating rows until someone finds a "roles" table with fifty identical "admin" entries.
Real data in the seed file. Exporting a slice of production and committing it as a seed is a data leak waiting to happen, and it goes stale the moment the schema shifts.
Seed logic that belongs in a stored procedure. If "reset to known state" logic needs to run from the app, a script, and a test harness, a stored procedure callable from all three beats three copies of the same script.
FAQ
Is a seed file the same as a fixture?
Related, not identical. A seed file typically populates a dev or demo database with realistic baseline data. A fixture usually feeds one specific test exactly the data it needs, often torn down afterward.
Should seed files run in production?
Rarely, and only for reference data the app cannot run without — a list of currencies, not a demo user. Gate any destructive seed logic so it cannot execute outside development.
What happens if a seed script fails halfway through?
It depends on whether it runs inside a transaction. One BEGIN/COMMIT block around the whole script rolls back everything inserted so far, rather than leaving a half-seeded database.
Where to go next