Standard scripts for Payload collections for faster development
Seed and clear scripts look like housekeeping. In practice they are closer to infrastructure, because together they decide how long it takes to go from an empty database to a screen you can actually design against.
{
"scripts": {
"seed:blog": "tsx src/scripts/Blog/seed-blog.ts",
"clear:blog": "tsx src/scripts/Blog/clear-blog.ts",
"seed:portfolio": "tsx src/scripts/Portfolio/seed-portfolio.ts",
"clear:portfolio": "tsx src/scripts/Portfolio/clear-portfolio.ts",
"sync:media": "tsx src/scripts/Media/sync-media.ts"
}
}Most projects treat them as an afterthought and pay for it every week. What follows is what they actually buy you, and how to write the two that matter most: seed and clear.
Why these scripts are worth the time
Your local database dies more often than you expect, especially while the schema is still moving. You change a field type and the push fails halfway through. You misspell a word in a file name and the table ends up declared under a different name. Those are the ordinary accidents of building a collection, and any one of them can leave the database in a state you cannot work from. With a seed, repopulating it is one command and a few seconds. The value is not in preventing those situations, it is in how cheaply you recover from them.
The second argument is about quality, and it is the one people miss. Hand-mocked data is always too clean. Three records when production will have forty, titles that happen to fit on one line, every image present, every optional field filled. Realistic sample data breaks your layout in the places it was always going to break: the title that wraps to three lines, the entry with no gallery, the one with the maximum four, the category label that is twice as long in German. Designing against those cases on your own machine is what makes the result hold up in front of a client.
How seeding should work
// Bypasses everything the CMS does on the way in. The row exists, and the
// admin panel cannot open it.
await db.insert(posts).values({ title: 'Hello', category: 'frontend' })
// Takes the same path the admin panel takes: beforeChange hooks run, the slug
// is generated, required fields are checked, the cache is revalidated.
await payload.create({
collection: 'posts',
locale: 'en',
overrideAccess: true,
data: { title: 'Hello', category: 'frontend' },
})Start by organizing the files. Keep the entries outside the script and outside one giant json. A script with forty records embedded in it is a file nobody wants to touch. Put the content in its own files, one per record, and have the script read the folder of entries. The payoff is that the folder becomes the source of truth, and editing one entry cannot break another.
Check the file before opening a connection. The reason is not performance, it is that a script which fails halfway leaves the collection in a state nobody asked for, half seeded and awkward to reason about. Three checks cover every new entry: that the required fields are present, that any enum value exists in the catalog of the field it belongs to, and that localized arrays have the same length in every language.
If the collection is localized, and it almost always is, creating a record is more than one operation. Start with the default locale and everything in it, then update that record in each remaining locale with only the localized fields. Sending the non localized fields again in the second pass is harmless but misleading, because it suggests they vary by language when the schema says they do not.
Print what the script is about to do before it does it. The files it found in the folder, the entries it is skipping because they already exist, and the ones it is about to create. A seed that reports its own plan is a seed you can run without opening the admin panel afterwards to check whether it worked.
How clear should work
Now the other half. A clear script does nothing the first time you run it: it connects, counts what it would remove, prints the list of records about to go, and exits. Deleting anything requires passing an explicit flag, and the one we use is --yes.
That first run is the line that actually prevents the accident, because of the question it answers. Which database is the script about to touch? A connection string lives in an environment variable, so the script knows exactly where it is pointing and you do not. Print the hostname, the port and the database name, never the credentials. Three words of output turn an invisible assumption into something you read before confirming.
On the second run, --yes deletes. That is the whole mechanism, and it is simple enough to add to any script in ten minutes. It is also what saves you the afternoon it would otherwise take to recreate a set of records that cost hours to produce in the first place.
Other cases where a script buys you peace of mind
Seed and clear cover any collection whose content lives entirely in the database. Some cases do not fit that shape, and those need a verb of their own.
The first is sync:media. A Payload upload collection stores metadata, a filename, dimensions, a mime type, while the actual bytes live in an S3 or R2 bucket. The two can drift apart in one direction very easily: the database loses every media record and the bucket still holds every file, perfectly intact. A reseed does not help, because the files are not in the repository. What helps is a script that lists the bucket and recreates a record for every object that no longer has one.
The second is a pair of checkpoint scripts, generate-backup and restore-backup. A database you can back up in three or four seconds is a database you can work on freely, and being able to stop worrying about it is exactly what you need to move faster on the part that matters.
One writes a new local backup, the other restores it and puts you back on that checkpoint. Having checkpoints on the database is what lets you try a schema change, watch it fail, and be back where you started a minute later instead of an hour later.
Safety is not the goal in itself, reversibility is, because reversibility is what lets you stop thinking about the command and go back to thinking about the work. A script you are afraid to run is a script you postpone, work around, and eventually run in a hurry against the wrong thing.

