You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/migration-safety.md
+74Lines changed: 74 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -107,6 +107,80 @@ The simplest solution here is to keep fields that we want to remove and avoid mi
107
107
108
108
This avoids needing the migration at all. It would be ideal to remove the data since we would be making the decision that we don't need it anymore but it is an acceptable trade off.
109
109
110
+
## Writing Custom Migrations to Replace Unsafe Auto-Generated Ones
111
+
112
+
Sometimes Payload/Drizzle generates migrations that would cause data loss in production due to the `PRAGMA foreign_keys=OFF` issue. In these cases, you need to:
113
+
1. Manually simplify the migration to avoid the unsafe patterns
114
+
2. Preserve the JSON schema snapshot so Payload's diffing logic recognizes the schema is in sync
115
+
116
+
### Understanding Payload's Migration System
117
+
118
+
Payload uses Drizzle under the hood, which tracks the database schema state using JSON snapshot files:
119
+
- Each migration has **two files**: `{timestamp}_{name}.ts` and `{timestamp}_{name}.json`
120
+
- The **TypeScript file** contains the migration SQL commands
121
+
- The **JSON file** is a snapshot of the **expected schema state after the migration runs**
122
+
- When you run `pnpm payload migrate:create`, Drizzle compares your Payload config against the **latest JSON snapshot** to detect changes
123
+
124
+
### Write your own, simplified migration
125
+
126
+
#### Step 1: Analyze what actually needs to change
127
+
128
+
Look at the auto-generated migration and identify the **actual schema change**. Often, a massive migration is just Drizzle's way of making a small change.
129
+
130
+
One liner (compares the two most recent migrations): `diff -u --color=always $(ls -t src/migrations/*.json | sed -n '2p') $(ls -t src/migrations/*.json | sed -n '1p')`
131
+
132
+
#### Step 2: Write a minimal, safe migration
133
+
134
+
Replace the auto-generated migration using `await db.run()` sql statements or using the Payload Local API.
The JSON file represents the **target schema state** that Payload expects after running your migration.
157
+
158
+
#### Step 4: Verify the schema is in sync
159
+
160
+
Test that Payload's diffing logic recognizes your custom migration as correct:
161
+
162
+
```bash
163
+
pnpm payload migrate:create test_check
164
+
```
165
+
166
+
You should see: **"No schema changes detected. Would you like to create a blank migration file?"**
167
+
168
+
If Payload tries to generate another migration, it means your JSON snapshot doesn't match what Payload expects. You may need to regenerate the JSON file (Step 3).
169
+
170
+
#### Step 5: Test the migration
171
+
172
+
Follow the "Testing a migration locally" workflow above using a local Turso server to ensure:
173
+
- The migration runs without errors
174
+
- No data is lost
175
+
- The schema state matches expectations
176
+
177
+
### When you can't write your own migration avoiding the table recreation pattern
178
+
179
+
If the migration genuinely needs to modify table structures in ways that require `PRAGMA foreign_keys=OFF`:
180
+
181
+
Keep the old schema and mark fields as `hidden: true` (see "The fix: keep old attributes" above)
0 commit comments