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.
This page covers the parts you reach for once the basics work: sending an asset through a collection's own hook instead of a plain upload, skipping a seed when its environment isn't ready, and stopping a bulk run from firing a revalidation per doc. The last section explains the engine, in case you're debugging or curious.
For the day-to-day API — defineSeed, ref, file, and _file — see
Writing seeds. For the four ways to trigger a run, see
Running the seed.
Custom ingestion
Most collections take a plain Payload upload for their _file and seed natively. A few ingest their file
through their own hook instead — e.g. @pro-laico/payload-mux's
mux-video, whose bytes go to Mux. Mark such a collection's own config and the engine hands the _file
to that hook (via a sourceField) rather than uploading bytes:
// the collection's own config — usually set by its plugin, not by you
{
slug: 'mux-video',
custom: {
seedAsset: { sourceField: 'source' }, // or `seedAsset: true` for the defaults
},
fields: [
// the field the engine writes `{ file, ...options }` to, read by the ingest hook
{ name: 'source', type: 'json', admin: { hidden: true } },
// ...the rest of the collection
],
}seedAssettrue | { sourceField?, subdir? }
Set on the collection's custom.seedAsset. true uses the defaults below; pass an object to override them.
sourceFieldstringdefault 'source'The field the engine sets to { file, ...options } for the collection's ingest hook to read.
subdirstringdefault <collection slug>Source-file folder under assetsDir. Override per-collection with the plugin's assetSubDirs.
The engine auto-discovers the marker from the live config — nothing to register, and the owning plugin
never imports payload-seed. The doc then seeds like any other (_file + ref()). Collections whose asset
is a plain upload (payload-fonts' fontOriginal, payload-images' image) need no marker. See
payload-mux → Seeding for a worked example.
Disabled seeds
Some collections can't always seed — @pro-laico/payload-mux's mux-video
needs API credentials to ingest a clip. The collection declares it instead:
// set by the owning plugin when it can't ingest — e.g. payload-mux without MUX_* env vars
{
slug: 'mux-video',
custom: { seedDisabled: 'Mux credentials not set (MUX_TOKEN_ID / MUX_TOKEN_SECRET)' },
}The engine skips that definition (warning with the reason) and drops any optional field whose
ref() points at it — a required ref is a hard error. The definition stays registered, so types
don't change with the environment. Set the env vars and the next run seeds it and fills the dropped
refs back in.
To gate a definition yourself, defineSeed takes the same flag directly:
defineSeed('reports', build, { disabled: !process.env.REPORTS_API_KEY })Don't gate the definition on the environment by leaving it out of the definitions array. That
shifts the generated seed-ref types with the environment, so every ref() at it flips between valid
and error as the dev server regenerates types on boot. Register it unconditionally and let it be
skipped at runtime.
How it works
Every entry point runs the same engine. You don't need any of this to write seeds; it's here for when you're debugging or curious.
What a seed run does
Trigger a seed from any entry point and the engine runs the same fixed sequence — starting from your
in-memory defineSeed output and ending with a fully-populated database:
Build the model
Skip any disabled definition (its own disabled, or the collection's
custom.seedDisabled — warning per skip), then run each remaining builder to produce the collection
records (with their _file) and the globals. Optional fields whose ref() points at a skipped
definition are dropped (required ones error).
Validate
Every ref targets a real collection and resolves to a seeded doc, every _file sits on an upload or
custom.seedAsset collection, no duplicate _key within a collection, no unknown top-level fields. All
issues are collected and thrown at once, naming the collection, _key, and field.
Build the dependency graph & topo-sort
Every ref(collection, key) becomes an edge; a depth-first sort orders each doc after the docs it
references. A cycle is broken by deferring an optional field; an all-required cycle is a hard error naming it.
Clear the seeded collections
Upload collections and any collection with delete hooks clear via payload.delete so those hooks fire
(e.g. external-asset cleanup); plain collections are wiped directly.
Create docs in dependency order
Resolve each doc's ref tokens to real ids and deliver its _file — a native upload, or a source-field
value for a custom.seedAsset collection's ingest hook. A field deferred to break a cycle is created null.
Resolve deferred references
If a cycle was broken, set each deferred field now that every doc exists — one update per deferred field.
Update globals
Last, after every doc exists — resolving their refs too.
How typed refs are generated
The plugin injects a SeedRegistry into payload-types.ts via Payload's own typescript.postProcess
hook, riding the same generate:types command you already run. That makes the augmentation global
with no import, exactly like Payload's generated GeneratedTypes, so ref('services', 'consulting')
is checked against your real _keys. Without codegen, refs fall back to runtime validation: safe
either way, fully safe with it.
Disable revalidation
The engine sets context: { disableRevalidate: true } on every create, update, and delete. Have your
afterChange / afterDelete revalidation hooks check for it and skip, so a bulk seed doesn't fire a
revalidation per doc:
export const revalidatePost: CollectionAfterChangeHook = ({ doc, context }) => {
if (!context.disableRevalidate) revalidatePath(`/posts/${doc.slug}`)
return doc
}Next
- Writing seeds —
defineSeed, records,_file, and where files live. - Running the seed — the admin button, HTTP endpoint, CLI, and
seed(). - Troubleshooting — symptom-first fixes for the common failures.