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

  1. Restore the missing migration file from git history so its name matches the `migration.name` in the error, then re-run `db:migrate:down`.
  2. 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.
  3. 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.
  4. 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

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


AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12). Data as JSON: /api/errors/ee4411a74e96890e. Report an issue: GitHub.