SeedGen
Generate realistic seed data for your PostgreSQL database without writing a single factory.
SeedGen reads your schema, figures out the foreign keys, and fills your tables with data that actually looks right — emails in email columns, prices in price columns, dates that make sense. Point it at a database, get usable data in seconds.
Install
Or grab a prebuilt binary from the releases page. Linux, macOS, and Windows are all there (x86_64 and arm64).
From source:
Usage
# Default: 10 rows per table
# Reproducible — same seed, same data
# Use a built-in scenario
# Per-table row counts
# Dump to a SQL file instead of inserting
# Lifecycle simulation — data that grows, churns, and trends over time
# Preview the per-bucket plan without touching the database
Other commands:
What it actually does
You give it a database URL. SeedGen then:
- Reads your tables, columns, and foreign keys via
information_schema. - Sorts tables by dependency (Kahn's algorithm). Cyclic FKs get a two-phase insert.
- Picks a generator per column based on its name and type —
emailcolumns get emails,pricecolumns get money,slugcolumns get slugs. - Generates rows with a seeded PRNG (ChaCha8) and either inserts them or writes a SQL file.
The same seed always produces the same output. That's the main promise — if you ever see non-determinism for a given seed, it's a bug.
Example output
$ seedgen generate --url $DATABASE_URL --seed 42 --rows 20
Generated 60 rows across 3 tables in 102.55ms (seed: 42)
users 20 rows [3.84ms]
posts 20 rows [3.63ms]
comments 20 rows [3.43ms]
The SQL file output looks like this:
BEGIN;
SET session_replication_role = 'replica';
INSERT INTO "users" ("id", "email", "name", "bio", "is_active", "created_at") VALUES
(1, 'ian.armstrong@fastmail.com', 'Greg Cook', '...', TRUE, '2025-03-17T12:22:11'),
(2, 'john.myers@mail.com', 'Katelyn Hart', '...', FALSE, '2024-10-16T12:40:28'),
...;
SET session_replication_role = 'origin';
COMMIT;
Scenarios
Four templates ship in the box: ecommerce, saas, blog, social. Each one comes with sensible distributions — most orders are paid, a small fraction are pending, that kind of thing.
You can also write your own:
# my-scenario.yaml
seed: 42
tables:
users:
count: 100
overrides:
role:
distribution:
created_at:
range:
orders:
count: per_parent(users, 0..10)
overrides:
status:
distribution:
order_items:
count: per_parent(orders, 1..5)
Run it with seedgen generate -f my-scenario.yaml --url $DATABASE_URL.
Lifecycle simulation
Normal generation creates every row as if it appeared at once. Lifecycle mode
instead makes data evolve over a time window — adoption curves, churn, seasonal
spikes, and child rows that follow their parents in time. It's opt-in: add a
lifecycle: block and SeedGen runs bucket by bucket (day/week/month/quarter),
generating new rows, churning old ones, and timestamping everything coherently.
Without the block, behavior is exactly as before.
# lifecycle-ecommerce.yaml
seed: 42
lifecycle:
start:
end:
bucket: month
tables:
users:
growth: # linear | exponential | s_curve | logistic | custom
model: s_curve
initial: 10
capacity: 5000
rate: 0.15
churn:
rate: 0.03 # 3% of active users leave each month
grace_period: 2 # ...but not before they're 2 months old
column: is_active
value: false
overrides:
plan:
timeline: # distribution interpolated between keyframes
:
:
orders:
growth:
follows: users # ~3.2 orders per active user per month
ratio: 3.2
variance: 0.35
seasonality:
monthly:
temporal:
created_at:
after: users.created_at # always after the user signed up
offset: 1d..60d
The result has a real story: SELECT date_trunc('month', created_at), count(*) FROM users GROUP BY 1
shows an adoption curve, December outsells July, churned users stop ordering, and
no child row is ever timestamped before its parent. Same seed still gives the
same data.
Production statistics profiling
Faker data has the wrong shape: every status equally likely, prices uniformly
random, no nulls where production has 40%. Profiling fixes that without ever
copying a row. seedgen profile reads only aggregate statistics from a
production (or staging) database — counts, distributions, numeric ranges, null
rates, FK ratios — and writes them to a YAML profile. Then generate --profile
produces seed data shaped like production but fully synthetic and deterministic.
# Read statistics from a read-only replica (zero row-level data leaves the DB)
# Generate 1% of production size, deterministically, shaped like production
Nothing but aggregate numbers ever leaves the source database, enforced by a six-layer security model:
- Query whitelist — only
COUNT/MIN/MAX/AVG/STDDEV/PERCENTILE/low-cardinalityGROUP BY; neverSELECT *, never row-level reads, never writes. - Cardinality guard — value distributions are captured only for low-cardinality (enum-like) columns; a 48k-distinct
emailcolumn yields a count, never addresses. - Sensitive detection —
password,ssn,credit_card,token, … are auto-skipped (overridable with--include). - Dry-run & audit —
--dry-run-queriesprints every query first; each run writes.seedgen-profile-audit.log. - Offline mode — for fully air-gapped production: export one read-only query, have a DBA run it, import the result. SeedGen never connects to prod.
- Connection safety — read-only transaction + statement timeout; warns on superuser.
# Review the exact queries before running anything
# Offline / air-gapped: export → DBA runs it → import (SeedGen never touches prod)
After profile-based generation, SeedGen prints a compliance report comparing the
generated data back to the profile (distributions, ratios, null rates) with a
✓/✗ per check. The --scale factor preserves proportions: shrink the row
count and every table ratio stays intact.
GitHub Action
There's a composite action that grabs the binary and runs generate for you. Drop it into a workflow before your integration tests:
- uses: ff4f/seedgen@v1
with:
database_url: ${{ secrets.TEST_DATABASE_URL }}
scenario: ecommerce
seed: 42
truncate_first: true
Useful inputs: database_url (required), scenario, scenario_file, seed, rows, entities, locale, fast, truncate_first, version. The database_url is never echoed in logs.
For reproducible CI, pin both the action ref and the binary: uses: ff4f/seedgen@v1 with version: v0.2.0.
MCP server
If you're using Claude Desktop, Cursor, or another MCP-aware editor, you can let the model run SeedGen for you:
Then just ask: "Seed my database with 100 users and 500 orders, seed 42."
The server exposes five tools: introspect, generate, reset, list_scenarios, validate.
Performance
Measured on an M1 MacBook with cargo bench. These are mean times over 100 samples.
| Operation | Mean |
|---|---|
| Introspect (3 tables, 3 FKs, live PG 17) | 9.7 ms |
| Topological sort, 50 tables / 100 FKs | 15.7 µs |
| Generate 100 rows | 240 µs |
| Generate 1,000 rows | 2.4 ms |
| Generate 10,000 rows | 24.3 ms |
That's roughly 411k rows/sec for pure generation. End-to-end with inserts depends on your Postgres setup — locally, 60 rows across 3 tables with FK lookups and single-row inserts takes about 100 ms.
Supported databases
PostgreSQL 12 through 17 is the only stable target right now. CI runs against all six versions. MySQL and SQLite are on the roadmap but not implemented yet.
Status
Currently v0.3.0. PostgreSQL works end-to-end; production statistics profiling landed in this release.
| Capability | Status |
|---|---|
| PostgreSQL introspection + generation | ✅ |
| Determinism / FK / NOT NULL / UNIQUE invariants | ✅ |
| Direct INSERT + SQL file output | ✅ |
| Scenario files (counts, distributions, templates) | ✅ |
| Lifecycle simulation (growth, churn, seasonality, temporal) | ✅ |
Production statistics profiling (profile, scale, offline, compliance) |
✅ |
| MCP server (stdio) | ✅ |
COPY protocol output (--fast) |
⬜ |
| JSON output | ⬜ |
| HTTP+SSE MCP transport | ⬜ |
| MySQL / SQLite adapters | ⬜ |
COPY protocol output, JSON output, and HTTP+SSE MCP transport are planned next. MySQL and SQLite adapters are further out.
Contributing
PRs welcome — see CONTRIBUTING.md. Release notes live in CHANGELOG.md.
License
MIT — see LICENSE.
Acknowledgments
Built in the spirit of Supabase Seed, schema-seed, drizzle-seed, and Seedfast, with a focus on speed, zero config, and determinism.