Seeding & Test Data

Prisma Intermediate/Advanced

Chapter 4 · Seeding & Test Data

A fresh database is empty. Before you can click around the blog, try a new feature, or run the tests in the next chapter, you need data in it — ideally the same data every time, created by one command. That's what a seed script is for.

Two Kinds of Seed Data

KindExamplesWhere it runs
Reference dataRequired categories, an admin account, default settingsEvery environment, including production
Sample dataFifty fake users, two hundred postsDevelopment and testing only

Keep them separate. Fake users in production are a real, and embarrassing, mistake.

Configuring the Seed Command

In Prisma 7 the seed command lives in the Prisma config file (prisma.config.ts, or prisma7.config.ts from 7.10 — see Prisma Fundamentals, Chapter 2), under migrations:

import "dotenv/config"; import { defineConfig, env } from "prisma/config"; export default defineConfig({ schema: "prisma/schema.prisma", migrations: { path: "prisma/migrations", seed: "tsx prisma/seed.ts", }, datasource: { url: env("DATABASE_URL") }, });
npm install -D tsx npx prisma db seed
Seeding is no longer automatic
Up to Prisma 6, migrate dev and migrate reset ran the seed script for you. Prisma 7 removed that. After resetting a database, run npx prisma db seed yourself — or add an npm script that does both, such as "db:fresh": "prisma migrate reset --force && prisma db seed".

A First Seed Script

// prisma/seed.ts import { prisma } from "../lib/prisma"; // the shared client + adapter from Prisma Fundamentals, Chapter 2 async function main() { await prisma.user.create({ data: { email: "ada@example.com", name: "Ada", role: "ADMIN", posts: { create: [{ title: "Hello", slug: "hello", content: "First post", published: true }] }, }, }); } main() .catch((e) => { console.error(e); process.exitCode = 1; }) .finally(() => prisma.$disconnect());

It works once. Run it a second time and it fails with P2002: ada@example.com already exists. A seed script that can only run on an empty database is fragile.

Idempotent Seeds

An idempotent script gives the same end result however many times you run it. There are two common ways to get there.

Option 1: upsert by a unique field

const tagNames = ["prisma", "orm", "typescript"]; for (const name of tagNames) { await prisma.tag.upsert({ where: { name }, update: {}, create: { name } }); } await prisma.user.upsert({ where: { email: "ada@example.com" }, update: { name: "Ada", role: "ADMIN" }, // keep it in the state we want create: { email: "ada@example.com", name: "Ada", role: "ADMIN" }, });

Best for reference data: it's safe to run against a database that already holds real data, because it only touches the rows it names.

Option 2: clear, then create

await prisma.$transaction([ prisma.post.deleteMany(), // children first... prisma.profile.deleteMany(), prisma.tag.deleteMany(), prisma.user.deleteMany(), // ...parents last ]);

Best for sample data in development. Delete children before parents, following each relation's onDelete rule. In the blog schema from Prisma Fundamentals, Post.author is required and uses the default rule, so deleting a user who still has posts fails with P2003; Profile and Comment are handled automatically (a profile is deleted with its user, comments are deleted with their post, and a comment's author is set to null when the user goes). Never run this against production — guard it:

if (process.env.NODE_ENV === "production") { throw new Error("Refusing to wipe data in production"); }

Realistic Fake Data With Faker

npm install -D @faker-js/faker
import { faker } from "@faker-js/faker"; faker.seed(42); // same "random" data on every run const tags = await prisma.tag.findMany(); for (let i = 0; i < 20; i++) { await prisma.user.create({ data: { email: faker.internet.email().toLowerCase(), name: faker.person.fullName(), posts: { create: Array.from({ length: faker.number.int({ min: 0, max: 6 }) }, (_, j) => ({ title: faker.lorem.sentence({ min: 3, max: 7 }), slug: `post-${i}-${j}`, content: faker.lorem.paragraphs(3), published: faker.datatype.boolean({ probability: 0.7 }), viewCount: faker.number.int({ max: 500 }), tags: { connect: faker.helpers.arrayElements(tags, { min: 0, max: 3 }).map((t) => ({ id: t.id })) }, })), }, }, }); }
Why seed the random generator?
faker.seed(42) makes Faker produce the same sequence every run. A bug you found in "the seeded data" will still be there tomorrow, and a teammate running the same script gets the same database. Change the number when you want a different set.
Random values can still collide
Faker can generate the same email twice. With a @unique column that throws P2002. The slugs above avoid it by building from the loop counters; for emails, add the index (`user${i}@example.com`) or use Faker's unique helpers where your version provides them.

Speed: createMany

One create per row means one database round trip per row. For flat data without nested relations, createMany inserts everything in one statement:

await prisma.tag.createMany({ data: ["prisma", "orm", "typescript", "sql"].map((name) => ({ name })), skipDuplicates: true, // ignore rows that would break a unique constraint });
createMany limits
createMany can't create nested relations, and skipDuplicates isn't supported on every database (SQLite, MongoDB and SQL Server don't support it). Check before relying on it; for those databases, use upsert or filter out existing rows first.

Organising a Bigger Seed

prisma/
├── seed.ts            <- decides what to run
└── seeds/
    ├── reference.ts   <- tags, admin user (upserts; safe anywhere)
    └── sample.ts      <- fake users and posts (dev/test only)
// prisma/seed.ts import { seedReference } from "./seeds/reference"; import { seedSample } from "./seeds/sample"; await seedReference(); if (process.env.NODE_ENV !== "production") { await seedSample(); }

Hands-On Exercises

Exercise 1

Write seeds/reference.ts: an admin user and five fixed tags, fully idempotent. Run it three times and show the row counts don't change.

📄 View solution
Exercise 2

Write seeds/sample.ts: clear the sample data, then create 10 users with 0–5 posts each using a seeded Faker. Make sure it can't run in production and never deletes the admin user or the reference tags.

📄 View solution
Exercise 3

Add npm scripts so that one command resets the database, applies every migration and seeds it. Explain why this matters more in Prisma 7 than it did in Prisma 6.

📄 View solution

Chapter 4 Quick Reference

  • Configure migrations.seed (e.g. "tsx prisma/seed.ts") in the Prisma config file
  • Run with npx prisma db seed — Prisma 7 no longer seeds automatically after migrate dev/reset
  • Reference data: upsert by a unique field; safe in every environment
  • Sample data: delete children before parents, then create; guard against production
  • faker.seed(n) gives repeatable fake data; build unique fields from counters
  • createMany for flat bulk inserts; no nested relations; skipDuplicates isn't available on every database