Payload Plugins
Pluginspayload-seed

Running the seed

Run your seed from wherever you are — the admin button, an HTTP call, the CLI, or straight from code.

For AI / LLMs: View Markdown

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 pointHow you trigger itENABLE_SEED gateNeeds a user
Admin buttonClick "Seed your database" in the admin headerYesYes
HTTP endpointPOST /api/seedYesYes
CLIpayload seed (Local API)YesNo
Programmaticseed() in codeNoNo

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_SEED must equal "true", or it returns 403 and 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 with ENABLE_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

StatusBodyWhen
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.
403The 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.

FieldTypeWhat
createdRecord<slug, number>Count of docs created per collection.
collectionsstring[]Slugs of the collections seeded this run.
globalsstring[]Slugs of the globals seeded this run.
orderstring[]Topo-sorted create order, e.g. ['media:hero', 'services:consulting', 'posts:launch'].
deferredFields 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 seedsdefineSeed, records, refs, and files.
  • Advanced — custom ingestion, disabled seeds, how the engine works, and disabling revalidation.
  • Troubleshooting — the node:crypto?tsx-namespace CLI bug and other failure modes.

On this page