# Running the seed

URL: /docs/plugins/payload-seed/running

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](/docs/plugins/payload-seed#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.

```ts
// 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.

```bash
# 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:

```json
// package.json
{ "scripts": { "seed": "payload seed" } }
```

```bash
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](/docs/plugins/payload-seed/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:

```ts
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](/docs/plugins/payload-seed/writing-seeds) — `defineSeed`, records, refs, and files.
- [Advanced](/docs/plugins/payload-seed/advanced) — custom ingestion, disabled seeds, how the engine works, and disabling revalidation.
- [Troubleshooting](/docs/plugins/payload-seed/troubleshooting) — the `node:crypto?tsx-namespace` CLI bug and other failure modes.
