Generating Payload Migrations
This skill covers the end-to-end process for creating a Payload CMS database migration. It keeps schema changes source-driven, migration names descriptive, generated artifacts reviewed, and migrations from being run automatically.
Step 1 - Update Payload Source Files
Modify the relevant Payload source of truth first:
- Collections:
src/collections/<CollectionName>.ts - Globals, if present:
src/globals/ - Payload config:
src/payload.config.ts - Related field helpers, hooks, access logic, or constants when they affect schema
Use Payload collection/global/field config as the source of truth. Do not hand-write migration SQL for schema changes that Payload can generate from config.
Step 2 - Generate the Migration
Run the migration creation command from the project root with a clear snake_case slug:
npm run migrate:create <migration_name>
The name becomes part of the generated files under src/migrations/:
src/migrations/<timestamp>_<migration_name>.tssrc/migrations/<timestamp>_<migration_name>.json
Choose the name carefully before generating. Use action-oriented names:
create_- creating a new collection/tableadd_- adding a field, index, relationship, enum value, or constraintremove_/drop_- removing a database objectrename_- renaming a field or collection-backed objectupdate_/change_- modifying existing schema shapemigrate_- transforming or moving existing data
Real project example:
npm run migrate:create create_regattas_collection
This generated:
src/migrations/20260707_125121_create_regattas_collection.tssrc/migrations/20260707_125121_create_regattas_collection.json
Step 3 - Review Generated Artifacts
Open the generated .ts, .json, and src/migrations/index.ts.
Verify the TypeScript migration:
up()contains only the expected schema changes.down()correctly reverses those changes.- No unrelated collections, tables, fields, enums, indexes, or constraints are touched.
Verify the JSON snapshot:
- It includes the expected new or changed schema state.
- It does not contain surprising unrelated schema drift.
Verify src/migrations/index.ts:
- The generated migration is imported.
- The
migrationsarray pointsup,down, andnameat the generated migration. - The migration
namematches the generated filename without extension.
For the create_regattas_collection example, the expected changes were:
- Create
regattas. - Create
enum_regattas_countryandenum_regattas_cup. - Add
payload_locked_documents_rels.regattas_id. - Add indexes for
regattas.updated_at,regattas.created_at, and the lock relation. - Add the lock relation foreign key.
If the diff contains unrelated changes, inspect other Payload config or collection edits before keeping the migration.
Step 4 - Handle Manual SQL Only When Needed
Use generated migrations by default. If Payload cannot represent the required database object, create or edit a migration intentionally and keep the SQL minimal.
Common examples:
- Custom Postgres functions or triggers
- Expression or partial indexes not represented by Payload config
- Data backfills or data transformations
- Database objects outside Payload's schema model
For manual SQL:
- Keep
up()anddown()symmetrical. - Add only the custom SQL required for the change.
- Leave Payload collection/global config as the source of truth for representable schema.
- Add a concise code comment near the relevant config only if the database object cannot be discovered from Payload config.
Step 5 - Stop Here. Do Not Run the Migration
Never run npm run migrate or any migration execution command automatically. The developer runs migrations manually at the right time after coordinating database state.
Do not suggest running the migration as a verification step. Leave the generated files ready and report what was created and reviewed.
In the final response, always include the manual run command in a copy-ready fenced block:
npm run migrate
Label it as a manual command for the developer to run when ready.
Quick Reference
| Command | Purpose |
|---|---|
npm run migrate:create <migration_name> |
Generate a Payload migration |
npm run payload -- migrate:create <name> |
Underlying Payload command |
npm run migrate |
Run manually only - never automated |
npm run payload -- migrate:status |
Inspect migration state when requested |
Key Paths
- Payload config:
src/payload.config.ts - Collections:
src/collections/ - Migrations:
src/migrations/ - Migration registry:
src/migrations/index.ts - Generated Payload types:
src/payload-types.ts