Simplify your stack and build anything. Or everything.
Build tomorrow’s web with a modern solution you truly own.
Code-based nature means you can build on top of it to power anything.
It’s time to take back your content infrastructure.

Migrations

Payload exposes a full suite of migration controls available for your use. Migration commands are accessible via the npm run payload command in your project directory.

Ensure you have an npm script called "payload" in your package.json file.

1
{
2
"scripts": {
3
"payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload"
4
}
5
}

Migration file contents

Payload stores all created migrations in a folder that you can specify. By default, migrations are stored in ./src/migrations.

A migration file has two exports - an up function, which is called when a migration is executed, and a down function that will be called if for some reason the migration fails to complete successfully. The up function should contain all changes that you attempt to make within the migration, and the down should ideally revert any changes you make.

Here is an example migration file:

1
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/your-db-adapter'
2
3
export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
4
// Perform changes to your database here.
5
// You have access to `payload` as an argument, and
6
// everything is done in TypeScript.
7
}
8
9
export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
10
// Do whatever you need to revert changes if the `up` function fails
11
}

Using Transactions

When migrations are run, each migration is performed in a new transaction for you. All you need to do is pass the req object to any local API or direct database calls, such as payload.db.updateMany(), to make database changes inside the transaction. Assuming no errors were thrown, the transaction is committed after your up or down function runs. If the migration errors at any point or fails to commit, it is caught and the transaction gets aborted. This way no change is made to the database if the migration fails.

Using database directly with the transaction

Additionally, you can bypass Payload's layer entirely and perform operations directly on your underlying database within the active transaction:

MongoDB:

1
import { type MigrateUpArgs } from '@payloadcms/db-mongodb'
2
3
export async function up({ session, payload, req }: MigrateUpArgs): Promise<void> {
4
const posts = await payload.db.collections.posts.collection.find({ session }).toArray()
5
}

Postgres:

1
import { type MigrateUpArgs, sql } from '@payloadcms/db-postgres'
2
3
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
4
const { rows: posts } = await db.execute(sql`SELECT * from posts`)
5
}

SQLite:

In SQLite, transactions are disabled by default. More.

1
import { type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
2
3
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
4
const { rows: posts } = await db.run(sql`SELECT * from posts`)
5
}

Migrations Directory

Each DB adapter has an optional property migrationDir where you can override where you want your migrations to be stored/read. If this is not specified, Payload will check the default and possibly make a best effort to find your migrations directory by searching in common locations ie. ./src/migrations, ./dist/migrations, ./migrations, etc.

All database adapters should implement similar migration patterns, but there will be small differences based on the adapter and its specific needs. Below is a list of all migration commands that should be supported by your database adapter.

Commands

Migrate

The migrate command will run any migrations that have not yet been run.

1
npm run payload migrate

Create

Create a new migration file in the migrations directory. You can optionally name the migration that will be created. By default, migrations will be named using a timestamp.

1
npm run payload migrate:create optional-name-here

Status

The migrate:status command will check the status of migrations and output a table of which migrations have been run, and which migrations have not yet run.

payload migrate:status

1
npm run payload migrate:status

Down

Roll back the last batch of migrations.

1
npm run payload migrate:down

Refresh

Roll back all migrations that have been run, and run them again.

1
npm run payload migrate:refresh

Reset

Roll back all migrations.

1
npm run payload migrate:reset

Fresh

Drops all entities from the database and re-runs all migrations from scratch.

1
npm run payload migrate:fresh

When to run migrations

Depending on which Database Adapter you use, your migration workflow might differ subtly.

In relational databases, migrations will be required for non-development database environments. But with MongoDB, you might only need to run migrations once in a while (or never even need them).

MongoDB

In MongoDB, you'll only ever really need to run migrations for times where you change your database shape, and you have lots of existing data that you'd like to transform from Shape A to Shape B.

In this case, you can create a migration by running pnpm payload migrate:create, and then write the logic that you need to perform to migrate your documents to their new shape. You can then either run your migrations in CI before you build / deploy, or you can run them locally, against your production database, by using your production database connection string on your local computer and running the pnpm payload migrate command.

Postgres

In relational databases like Postgres, migrations are a bit more important, because each time you add a new field or a new collection, you'll need to update the shape of your database to match your Payload Config (otherwise you'll see errors upon trying to read / write your data).

That means that Postgres users of Payload should become familiar with the entire migration workflow from top to bottom.

Here is an overview of a common workflow for working locally against a development database, creating migrations, and then running migrations against your production database before deploying.

1 - work locally using push mode

Payload uses Drizzle ORM's powerful push mode to automatically sync data changes to your database for you while in development mode. By default, this is enabled and is the suggested workflow to using Postgres and Payload while doing local development.

You can disable this setting and solely use migrations to manage your local development database (pass push: false to your Postgres adapter), but if you do disable it, you may see frequent errors while running development mode. This is because Payload will have updated to your new data shape, but your local database will not have updated.

For this reason, we suggest that you leave push as its default setting and treat your local dev database as a sandbox.

For more information about push mode and prototyping in development, click here.

The typical workflow in Payload is to build out your Payload configs, install plugins, and make progress in development mode - allowing Drizzle to push your changes to your local database for you. Once you're finished, you can create a migration.

But importantly, you do not need to run migrations against your development database, because Drizzle will have already pushed your changes to your database for you.

2 - create a migration

Once you're done with working in your Payload Config, you can create a migration. It's best practice to try and complete a specific task or fully build out a feature before you create a migration.

But once you're ready, you can run pnpm payload migrate:create, which will perform the following steps for you:

  • We will look for any existing migrations, and automatically generate SQL changes necessary to convert your schema from its prior state to the new state of your Payload Config
  • We will then create a new migration file in your /migrations folder that contains all the SQL necessary to be run

We won't immediately run this migration for you, however.

3 - set up your build process to run migrations

Generally, you want to run migrations before you build Payload for production. This typically happens in your CI pipeline and can usually be configured on platforms like Payload Cloud, Vercel, or Netlify by specifying your build script.

A common set of scripts in a package.json, set up to run migrations in CI, might look like this:

1
"scripts": {
2
// For running in dev mode
3
"dev": "next dev --turbo",
4
5
// To build your Next + Payload app for production
6
"build": "next build",
7
8
// A "tie-in" to Payload's CLI for convenience
9
// this helps you run `pnpm payload migrate:create` and similar
10
"payload": "cross-env NODE_OPTIONS=--no-deprecation payload",
11
12
// This command is what you'd set your `build script` to.
13
// Notice how it runs `payload migrate` and then `pnpm build`?
14
// This will run all migrations for you before building, in your CI,
15
// against your production database
16
"ci": "payload migrate && pnpm build",
17
},

In the example above, we've specified a ci script which we can use as our "build script" in the platform that we are deploying to production with.

This will require that your build pipeline can connect to your database, and it will simply run the payload migrate command prior to starting the build process. By calling payload migrate, Payload will automatically execute any migrations in your /migrations folder that have not yet been executed against your production database, in the order that they were created.

If it fails, the deployment will be rejected. But now, with your build script set up to run your migrations, you will be all set! Next time you deploy, your CI will execute the required migrations for you, and your database will be caught up with the shape that your Payload Config requires.

Running migrations in production

In certain cases, you might want to run migrations at runtime when the server starts. Running them during build time may be impossible due to not having access to your database connection while building or similar reasoning.

If you're using a long-running server or container where your Node server starts up one time and then stays initialized, you might prefer to run migrations on server startup instead of within your CI.

In order to run migrations at runtime, on initialization, you can pass your migrations to your database adapter under the prodMigrations key as follows:

1
// Import your migrations from the `index.ts` file
2
// that Payload generates for you
3
import { migrations } from './migrations'
4
import { buildConfig } from 'payload'
5
6
export default buildConfig({
7
// your config here
8
db: postgresAdapter({
9
// your adapter config here
10
prodMigrations: migrations
11
})
12
})

Passing your migrations as shown above will tell Payload, in production only, to execute any migrations that need to be run prior to completing the initialization of Payload. This is ideal for long-running services where Payload will only be initialized at startup.

Next

Transactions