Drizzle seed data
Seed a Drizzle + PostgreSQL app with realistic rows, drizzle-kit push, then one command. Foreign keys resolve themselves.
Drizzle apps seed data through drizzle-kit migrations plus whatever seed script the team writes, usually a seed.ts file full of db.insert(table).values() calls. It works until relations and constraints outgrow the script. This guide shows the Drizzle-native workflow, where it breaks, and the schema-driven alternative.
How Drizzle seeding normally works
Drizzle keeps the schema in TypeScript. Migrations are generated with drizzle-kit generate and applied with drizzle-kit push (dev) or drizzle-kit migrate (deploy). Seeding is a plain script:
// seed.ts
import { drizzle } from "drizzle-orm/node-postgres";
import { users } from "./schema";
import { faker } from "@faker-js/faker";
const db = drizzle(process.env.DATABASE_URL!);
for (let i = 0; i < 50; i++) {
await db.insert(users).values({
email: faker.internet.email(),
name: faker.person.fullName(),
});
}Fine for a single table. The moment orders references users, the script has to manage ids by hand, insert the user, read the id back, then insert the order. Every relation added to the schema is another layer of manual bookkeeping in the seed.
Where Drizzle seed scripts break
- Foreign keys. Children need parent ids that don't exist until the parent is inserted. The script grows
RETURNINGclauses and lookup queries, and the dependency order becomes implicit, invisible until a seed fails or, worse, silently produces orphaned rows. - Schema drift. A new NOT NULL column, an enum, a CHECK constraint, each one is a seed change. Drizzle's schema is in TypeScript and checked by the compiler; the seed script is not. The schema moves, the seed doesn't, and tests start failing for reasons nobody can explain.
- Uniform randomness.
faker.internet.email()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 same one Drizzle pushed, and generates rows from it. The Drizzle-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 RETURNING clauses.
The schema is the seed
When the Drizzle schema changes and you push a new migration, the generated data changes with it. No seed file to update, because nothing in the seed duplicated the schema.
Drizzle + 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 Drizzle seed command do you need?
| Goal | Command |
|---|---|
| Emit a new SQL migration from the TypeScript schema | npx drizzle-kit generate |
| Apply the schema directly, no migration files | npx drizzle-kit push |
| Apply pending migrations in deploy or CI | npx drizzle-kit migrate |
| Write a Drizzle schema from an existing database | npx drizzle-kit pull |
| Fill tables from the schema, in code | drizzle-seed |
| Generate a production-shaped dataset with no seed code | npx --yes @weavori/cli generate postgres://localhost:5432/mydb |
npx drizzle-kit generate
drizzle-kit generate diffs your TypeScript schema against the last migration and emits a new SQL migration file. It evolves the schema, not the data, seeding is a separate step that runs after the migration lands.
npx drizzle-kit push
drizzle-kit push applies the schema straight to the database with no migration files, the local-dev shortcut. Push, then seed; both steps take the same schema as input, which is why a schema-driven generator needs no extra configuration to work against it.
npx drizzle-kit migrate
drizzle-kit migrate applies any pending migrations to the database; use its verbose output when you need to see each statement. Migration health is schema health, seed data always runs after migrations succeed.
npx drizzle-kit pull
drizzle-kit pull is the reverse of generate: it introspects an existing database and writes a Drizzle schema that matches it. Handy when the database came first, either way the schema is the input, and that is exactly what a schema-driven generator reads.
drizzle-seed
drizzle-seed, Drizzle's own library, fills tables from your schema and links related rows from a deterministic, reproducible seed. Weavori takes the database route: it introspects the live schema and writes FK-intact rows via COPY, sampling pg_stats so distributions match production. Reach for drizzle-seed when you want deterministic, in-code generation you can assert against in unit tests; reach for Weavori when you want production-shaped data at scale with no seed code to maintain.
Ids are a schema concern either way: if your columns default to uuid or nanoid, the database or app layer produces them. The constraint that matters is referential, every child row must reference a parent id that actually exists. Weavori generates primary keys and foreign-key references together, so children point at real parents by construction.
When to keep your Drizzle seed
Honesty about boundaries: keep the seed script for deterministic unit-test fixtures: a known user the test asserts against. Keep @faker-js/faker for single values inside tests. Weavori replaces the dataset problem: filling dev, staging, and CI databases with production-shaped, FK-intact rows.
Related guides
- 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
- Prisma seed data and TypeORM seed data: the same loop in other stacks
- Quickstart: first seed in minutes