Running the seed
Run your seed from wherever you are — the admin button, an HTTP call, the CLI, or straight from code.
Four entry points run the same seed engine over the same definitions. Pick the one that fits where you are.
Three of them — the admin button, the HTTP endpoint, and the CLI — sit behind the ENABLE_SEED kill switch. If ENABLE_SEED isn't exactly "true", they refuse to run. The fourth, seed(), is the in-code path and is deliberately not gated, which is what lets a test drive the real seed.
See the Quickstart for setting ENABLE_SEED.
Every entry point is destructive. It clears the seeded collections before recreating them, so run
it on purpose. Leave ENABLE_SEED unset in production.
The four entry points
| Entry point | How you trigger it | ENABLE_SEED gate | Needs a user |
|---|---|---|---|
| Admin button | Click "Seed your database" in the admin header | Yes | Yes |
| HTTP endpoint | POST /api/seed | Yes | Yes |
| CLI | payload seed (Local API) | Yes | No |
| Programmatic | seed() in code | No | No |
Do you need a user first? The admin button and POST /api/seed require a logged-in Payload user
(any user — not just an admin). The CLI and seed() run over the Local API with access control
bypassed, so they need no user at all — reach for them to bootstrap an empty database (you can even
seed your first admin user as part of the run).
Admin button
The friendliest path: a "Seed your database" button in the admin header. The plugin registers it
for you — set ENABLE_SEED=true, then click it. It POSTs to /api/seed as the logged-in user and
reports success (or the error) inline.
// payload.config.ts
seedPlugin({ definitions: [media, services, posts] })When ENABLE_SEED isn't set, the button doesn't render at all — environments where the endpoint would
refuse anyway never show it.
Best for: local development and demos — the quickest way to reseed while you build.
HTTP endpoint
The plugin registers POST /api/seed — a Payload REST route, and the very route the admin button
calls. Hit it from a script, a CI step, or curl against a running app. It's guarded twice; both must
pass:
ENABLE_SEEDmust equal"true", or it returns403and does nothing (the primary safety — leave it unset in production and the route is inert).- The request must be authenticated — a Payload auth cookie or API key — or
403. Any logged-in user qualifies, so gate by environment withENABLE_SEED, not by role.
# Send your Payload auth cookie or API key (here: an API key on the `users` collection).
curl -X POST https://your-app.com/api/seed \
-H 'Authorization: users API-Key <your-api-key>'
# 200 → { "success": true, "created": { "media": 3, "services": 2, "posts": 1 }, "order": [ … ] }Responses
| Status | Body | When |
|---|---|---|
200 | { success, created, order } | Seed ran and committed. |
400 | { error, issues } | Validation failed — bad ref, duplicate _key, unknown field or slug. The same named, collected issues the engine produces, so you can fix them without digging through server logs. |
403 | — | The gate is closed (ENABLE_SEED ≠ "true"), or the request wasn't authenticated. |
500 | { error: 'Error seeding data.' } | Any other failure. It's logged server-side; internals never reach the client. |
Which 403 is it? An unauthenticated request fails on auth. A request that carries a valid auth
cookie or API key and still gets 403 means the ENABLE_SEED gate is closed. Send credentials first,
then a lingering 403 points at the switch.
Best for: CI/CD, or seeding a deployed environment over HTTP.
CLI
The plugin adds a payload seed command (a bin on the package) that runs over the Local API — no
HTTP, no auth, just a terminal. Wire a script and run it with the switch on:
// package.json
{ "scripts": { "seed": "payload seed" } }ENABLE_SEED=true pnpm seed
# [payload-seed] clearing collections...
# [payload-seed] seeding documents...
# [payload-seed] seed complete.payload seed boots through Payload's tsx-based CLI. On some Node + database-adapter combinations
that loader has bugs — if it dies with node:crypto?tsx-namespace on Node 24, see
Troubleshooting. Use the admin button or endpoint instead
(they run in the app's own runtime).
Best for: a terminal, or a pre-deploy CI step that seeds before the app serves traffic.
Programmatic
Call the engine directly with seed() — for tests, migrations, or a custom script. It builds a Local
API req if you don't pass one and resolves the options you hand it. Unlike the other three it is not
behind ENABLE_SEED (the gate lives on the entry points), so a test can drive the real seed:
import { seed } from '@pro-laico/payload-seed'
import { getPayload } from 'payload'
import config from '@payload-config'
const payload = await getPayload({ config })
const result = await seed({ payload, options: { definitions: [media, services, posts] } })
result.created // → { media: 3, services: 2, posts: 1 } (created docs per collection)
result.order // → ['media:hero', 'services:consulting', 'posts:launch'] (topo-sorted create order)
result.deferred // → fields created null to break a ref cycle, set in the second pass
result.skipped // → [{ slug, reason }] definitions skipped this run (disabled / custom.seedDisabled)SeedResult fields
seed() returns { created, collections, globals, order, deferred, skipped } — the same shape the
endpoint response carries (alongside success: true) and the source the CLI's completion log summarizes.
| Field | Type | What |
|---|---|---|
created | Record<slug, number> | Count of docs created per collection. |
collections | string[] | Slugs of the collections seeded this run. |
globals | string[] | Slugs of the globals seeded this run. |
order | string[] | Topo-sorted create order, e.g. ['media:hero', 'services:consulting', 'posts:launch']. |
deferred | — | Fields created null to break a ref cycle, then set in the second pass. |
skipped | [{ slug, reason }] | Definitions skipped this run (their own disabled, or a collection's custom.seedDisabled). |
Still destructive; call it deliberately. Best for: integration tests and migrations.
Next
- Writing seeds —
defineSeed, records, refs, and files. - Advanced — custom ingestion, disabled seeds, how the engine works, and disabling revalidation.
- Troubleshooting — the
node:crypto?tsx-namespaceCLI bug and other failure modes.
Writing seeds
Describe your seed data in typed seed.ts files — refs, files, collections, and globals all checked against your real Payload types as you write.
Advanced
Route assets through a collection's own ingest hook, skip seeds that can't run in an environment, and keep bulk seeding from firing revalidation — plus how the engine orders and creates everything.