payloadcms/payload · error · Error
Migration ${migration.name} not found locally.
Error message
Migration ${migration.name} not found locally. What it means
Thrown by Payload's Drizzle `migrateDown` (rollback the latest batch) when a migration name recorded in the `payload-migrations` database table has no matching file among the locally-read migration files. The adapter reconciles DB state against the filesystem; an entry with no local `.ts` migration module cannot be rolled back because its `down()` function is unknown. It surfaces a drift between what the database believes was applied and what the code currently ships.
Source
Thrown at packages/drizzle/src/migrateDown.ts:38
const { existingMigrations, latestBatch } = await getMigrations({
payload,
})
if (!existingMigrations?.length) {
payload.logger.info({ msg: 'No migrations to rollback.' })
return
}
payload.logger.info({
msg: `Rolling back batch ${latestBatch} consisting of ${existingMigrations.length} migration(s).`,
})
const latestBatchMigrations = existingMigrations.filter(({ batch }) => batch === latestBatch)
for (const migration of latestBatchMigrations) {
const migrationFile = migrationFiles.find((m) => m.name === migration.name)
if (!migrationFile) {
throw new Error(`Migration ${migration.name} not found locally.`)
}
const start = Date.now()
const req = await createLocalReq({}, payload)
try {
payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })
await initTransaction(req)
const db = await getTransaction(this, req)
await migrationFile.down({ db, payload, req })
payload.logger.info({
msg: `Migrated down: ${migrationFile.name} (${Date.now() - start}ms)`,
})
const tableExists = await migrationTableExists(this, db)
if (tableExists) {
await payload.delete({View on GitHub (pinned to 00c58b35c0)
Solutions
- Restore the missing migration file from git history so its name matches the `migration.name` in the error, then re-run `db:migrate:down`.
- If the file was intentionally removed, delete the matching row from the `payload-migrations` table (or use `db:migrate:fresh` on a disposable dev DB) to clear the drift.
- Verify `migrationDir`/the adapter's migration path points at the same directory that was used to originally apply migrations, and that all migration files are committed.
- If you only want a clean slate in development, run `payload migrate:fresh` (drop + recreate + migrate up) instead of down.
Example fix
// before: migration file 0002_add_field was deleted from src/migrations but still in payload-migrations table // after: restore the file so names match // git checkout HEAD -- src/migrations/0002_add_field.ts // then re-run: // payload migrate:down
Defensive patterns
Strategy: validation
Validate before calling
// Before running migrateDown, verify every DB migration has a local file
import { readMigrationFiles } from 'payload'
const files = await readMigrationFiles({ payload })
const { existingMigrations } = await getMigrations({ payload })
const fileNames = new Set(files.map(f => f.name))
const missing = existingMigrations.filter(m => !fileNames.has(m.name))
if (missing.length) {
throw new Error(`Refusing to migrate down; local file missing for: ${missing.map(m => m.name).join(', ')}`)
}
await payload.db.migrateDown() Type guard
const isMigrationFile = (m: unknown): m is { name: string; up: Function; down: Function } =>
typeof m === 'object' && m !== null && typeof (m as any).name === 'string' &&
typeof (m as any).down === 'function' Try / catch
try {
await payload.db.migrateDown()
} catch (err) {
if (err instanceof Error && err.message.includes('not found locally')) {
payload.logger.error(`Migration drift detected. Restore missing files or run migrate:fresh on dev DB.`)
}
throw err
} Prevention
- Always commit generated migration files alongside their DB rows.
- Never delete migration files; deprecate them instead.
- Run migrations against the same migrationDir used to generate them.
- In CI, assert that the set of migration files is a superset of DB rows before deploying.
When it happens
Trigger: Calling `payload db:migrate:down` (or the equivalent `migrateDown` on the Drizzle adapter) when a row in the `payload-migrations` table references a migration whose file was deleted, renamed, never committed, or lives in a different `migrationDir` than the one `readMigrationFiles` scans.
Common situations: A teammate deleted or renamed a migration file but the DB row remained; running against a shared/staging database whose migrations were produced by an older branch; `migrationDir` misconfigured or pointing at a path that doesn't contain the applied migrations; a squashed migration history where old entries were removed locally but not from the DB.
Related errors
- Migration ${migration.name} not found locally.
- Migration ${migration.name} not found locally.
- No previous migration schema file found! A prior migration f
- Migration ${migration.name} not found locally.
- Either collection or globalSlug must be passed.
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/ee4411a74e96890e.
Report an issue: GitHub.