๐Ÿ“ฆ payloadcms / payload

๐Ÿ“„ migrations.mdx ยท 313 lines
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313---
title: Migrations
label: Migrations
order: 20
keywords: database, migrations, ddl, sql, mongodb, postgres, documentation, Content Management System, cms, headless, typescript, node, react, nextjs
desc: Payload features first-party database migrations all done in TypeScript.
---

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.

```json
{
  "scripts": {
    "payload": "cross-env PAYLOAD_CONFIG_PATH=src/payload.config.ts payload"
  }
}
```

<Banner>
  Note that you need to run Payload migrations through the package manager that
  you are using, because Payload should not be globally installed on your
  system.
</Banner>

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

```ts
import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/your-db-adapter'

export async function up({ payload, req }: MigrateUpArgs): Promise<void> {
  // Perform changes to your database here.
  // You have access to `payload` as an argument, and
  // everything is done in TypeScript.
}

export async function down({ payload, req }: MigrateDownArgs): Promise<void> {
  // Do whatever you need to revert changes if the `up` function fails
}
```

## Using Transactions

When migrations are run, each migration is performed in a new [transaction](/docs/database/transactions) for you. All
you need to do is pass the `req` object to any [Local API](/docs/local-api/overview) 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:

```ts
import { type MigrateUpArgs } from '@payloadcms/db-mongodb'

export async function up({
  session,
  payload,
  req,
}: MigrateUpArgs): Promise<void> {
  const posts = await payload.db.collections.posts.collection
    .find({ session })
    .toArray()
}
```

### Postgres:

```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-postgres'

export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
  const { rows: posts } = await db.execute(sql`SELECT * from posts`)
}
```

### SQLite:

In SQLite, transactions are disabled by default. [More](./transactions).

```ts
import { type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'

export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
  const { rows: posts } = await db.run(sql`SELECT * from posts`)
}
```

## 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.

```text
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.

```text
npm run payload migrate:create optional-name-here
```

Flags:

- `--skip-empty`: with Postgres, it skips the "no schema changes detected. Would you like to create a blank migration file?" prompt which can be useful for generating migration in CI.
- `--force-accept-warning`: accepts any command prompts, creates a blank migration even if there weren't any changes to the schema.

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

```text
npm run payload migrate:status
```

### Down

Roll back the last batch of migrations.

```text
npm run payload migrate:down
```

### Refresh

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

```text
npm run payload migrate:refresh
```

### Reset

Roll back all migrations.

```text
npm run payload migrate:reset
```

### Fresh

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

```text
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#mongodb-migrations

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#postgres-migrations

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](./postgres#prototyping-in-development-mode).

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.

<Banner type="warning">
  Warning: do not mix "push" and migrations with your local development
  database. If you use "push" locally, and then try to migrate, Payload will
  throw a warning, telling you that these two methods are not meant to be used
  interchangeably.
</Banner>

**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.

<Banner type="success">
  Tip: migrations created by Payload are relatively programmatic in nature, so
  there should not be any surprises, but before you check in the created
  migration it's a good idea to always double-check the contents of the
  migration files.
</Banner>

**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:

```js
  "scripts": {
    // For running in dev mode
    "dev": "next dev --turbo",

    // To build your Next + Payload app for production
    "build": "next build",

    // A "tie-in" to Payload's CLI for convenience
    // this helps you run `pnpm payload migrate:create` and similar
    "payload": "cross-env NODE_OPTIONS=--no-deprecation payload",

    // This command is what you'd set your `build script` to.
    // Notice how it runs `payload migrate` and then `pnpm build`?
    // This will run all migrations for you before building, in your CI,
    // against your production database
    "ci": "payload migrate && pnpm build",
  },
```

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:

```ts
// Import your migrations from the `index.ts` file
// that Payload generates for you
import { migrations } from './migrations'
import { buildConfig } from 'payload'

export default buildConfig({
  // your config here
  db: postgresAdapter({
    //  your adapter config here
    prodMigrations: migrations,
  }),
})
```

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.

<Banner type="warning">
  **Warning:** if Payload is instructed to run migrations in production, this
  may slow down serverless cold starts on platforms such as Vercel. Generally,
  this option should only be used for long-running servers / containers.
</Banner>

## Environment-Specific Configurations and Migrations

Your configuration may include environment-specific settings (e.g., enabling a plugin only in production). If you generate migrations without considering the environment, it can lead to discrepancies and issues. When running migrations locally, Payload uses the development environment, which might miss production-specific configurations. Similarly, running migrations in production could miss development-specific entities.

This is an easy oversight, so be mindful of any environment-specific logic in your config when handling migrations.

**Ways to address this:**

- Manually update your migration file after it is generated to include any environment-specific configurations.
- Temporarily enable any required production environment variables in your local setup when generating the migration to capture the necessary updates.
- Use separate migration files for each environment to ensure the correct migration is executed in the corresponding environment.