# Seeds (/docs/schema/seeds)

---
title: Seeds
description: A seed is a file that writes app data once. The runner wraps it in a transaction, records that it ran, and skips it every time after that.
kind: guide
package: questpie
---

A fresh database has your tables and none of the rows the app needs to work.
This page starts from one file in `seeds/` and ends with those rows written
once, on every machine that runs the app.

## The file

A seed lives under `seeds/`, next to `collections/`, and the file is the
registration. Default-export one `seed({...})` call per file.

```ts title="src/questpie/server/seeds/site-settings.ts"
import { seed } from "questpie/services";

export default seed({
	id: "siteSettings",
	description: "Write the default site settings",
	category: "required",
	async run({ globals, log }) {
		await globals.site_settings.update({
			siteName: "QUESTPIE",
			tagline: "Sharp cuts, every time",
		});

		log("Site settings written");
	},
});
```

`id` is what the runner records, so it has to be unique across your app and
every module in it. `category` is required, `description` is not.

`run` receives the whole app context. The same `collections`, `globals`, `db`,
`services`, `queue`, `email`, `storage` and `kv` a route or a job gets, plus
`log` for a line of CLI output and `createContext` for a request context of your
own.

`questpie add seed site-settings` scaffolds the file and runs codegen for you.

### It runs as the system

The runner puts every seed in system access mode, which bypasses collection,
global and field access rules, so bootstrap data can land before a user or a
role exists. HTTP requests are untouched by this and still run in user mode.

### Writing one locale at a time

`createContext({ locale })` builds a request context you pass as the second
argument to a CRUD call. A code that is not in your configured locales falls
back to the mapping in `locale.fallbacks`, then to your default locale.

```ts
const sk = await createContext({ locale: "sk" });
await globals.site_settings.update({ tagline: "Ostré strihy, vždy" }, sk);
```

## Running it

```bash
questpie generate   # register the new file in the typed app
questpie seed       # run everything that has not run yet
```

```txt
🌱 Found 1 seed(s)

🌱 Running 1 seed(s)...
  🌱 Running seed: siteSettings (Write the default site settings)
    Site settings written
  ✅ Seed completed: siteSettings
✅ All seeds completed successfully
```

The runner writes one row per finished seed into `questpie_seeds`, creating that
table on first use, and skips anything it finds there. Run the command again and
it prints `✅ No pending seeds`.

## What one run guarantees

A `seed({...})` body runs inside a single transaction, and the tracking row is
written in that same transaction. A seed that writes three rows and then throws
leaves nothing behind and stays pending. The run stops there, so the seeds after
it do not run either.

Write the body to be idempotent anyway, so a second entry changes nothing.
`--force`, `seed:reset`, an undo and a fresh database all send the runner back
through `run`. Look before you insert, or patch a singleton the way the example
above does.

## Categories

Every seed declares one of three.

| Category   | For                                                                           |
| ---------- | ----------------------------------------------------------------------------- |
| `required` | What every environment needs. Roles, baseline settings, the first invitation. |
| `dev`      | Demo content for a laptop or a preview deployment.                            |
| `test`     | Fixtures an integration suite can count on.                                   |

The CLI filter is literal. `--category dev` does not pull `required` in the way
`autoSeed: "dev"` does, and an unrecognised name is an error, not an empty run.

```bash
questpie seed --category required
questpie seed --category required,dev
```

## Order

`dependsOn` lists the ids that have to run first. The runner sorts the selected
seeds topologically before it runs any of them.

```ts
export default seed({
	id: "demoPosts",
	category: "dev",
	dependsOn: ["siteSettings"],
	async run({ collections }) {
		await collections.posts.create({ title: "Hello", slug: "hello" });
	},
});
```

A dependency outside your filter is pulled in regardless, so
`--only demoPosts` runs `siteSettings` first. An id that matches no seed logs a
warning and the run continues. A cycle throws
`Circular seed dependency detected at "…"`.

## Taking it back out

`undo` is optional. `questpie seed:undo` runs each handler inside a transaction,
then deletes the tracking row so the seed is pending again. It walks the
registered seeds backwards, and codegen registers them alphabetically by
filename, so that is not the reverse of the order they ran. `dependsOn` does not
reorder an undo.

```ts
async undo({ collections }) {
	await collections.posts.deleteMany({ where: { slug: { eq: "hello" } } });
},
```

A seed with no `undo` handler is left alone and stays recorded as executed.
`seed:reset` is the blunter tool: it clears tracking rows and touches no data,
so the next `questpie seed` treats those seeds as pending.

## When one seed is too big to restart

`seed({...})` is all-or-nothing, which is the wrong shape for a seed that
uploads two hundred files or calls an API you would rather not pay for twice.
`seed.steps({...})` trades the one transaction for a checkpoint per step.

**[Checkpointed seeds](/docs/schema/seeds/steps)** builds one and covers what
resuming costs you.

## The commands

| Command                | What it does                                           |
| ---------------------- | ------------------------------------------------------ |
| `questpie seed`        | Runs pending seeds.                                    |
| `questpie seed:status` | Prints what has run and what has not.                  |
| `questpie seed:undo`   | Runs `undo` handlers, then clears their tracking rows. |
| `questpie seed:reset`  | Clears tracking rows and step checkpoints. No data.    |

```bash
questpie seed:status
```

```txt
📊 Seed Status:

Executed: 1
Pending: 1

✅ Executed seeds:
  - siteSettings [required] (2026-08-01T09:12:44.010Z)

⏳ Pending seeds:
  - demoPosts [dev]
```

**[Running seeds](/docs/schema/seeds/running)** has the options each command
takes, the dry run, the config key that seeds on startup, and the same four
operations from code.

## Related

- **[Migrations](/docs/ship/migrations)** for the other half of the split. A
  migration changes tables and columns, a seed writes rows.
- **[Access control](/docs/schema/access-control)** for the rules system mode
  steps over.
- **[Modules](/docs/code/modules)** for shipping seeds in a package. Seed arrays
  concatenate, which is why ids have to be unique.
- **[Codegen](/docs/code/codegen)** for how `seeds/` is discovered.
