{"record":{"id":"ee4411a74e96890e","repo":"payloadcms/payload","slug":"migration-migration-name-not-found-locally","errorCode":null,"errorMessage":"Migration ${migration.name} not found locally.","messagePattern":"Migration (.+?) not found locally\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/drizzle/src/migrateDown.ts","lineNumber":38,"sourceCode":"  const { existingMigrations, latestBatch } = await getMigrations({\n    payload,\n  })\n\n  if (!existingMigrations?.length) {\n    payload.logger.info({ msg: 'No migrations to rollback.' })\n    return\n  }\n\n  payload.logger.info({\n    msg: `Rolling back batch ${latestBatch} consisting of ${existingMigrations.length} migration(s).`,\n  })\n\n  const latestBatchMigrations = existingMigrations.filter(({ batch }) => batch === latestBatch)\n\n  for (const migration of latestBatchMigrations) {\n    const migrationFile = migrationFiles.find((m) => m.name === migration.name)\n    if (!migrationFile) {\n      throw new Error(`Migration ${migration.name} not found locally.`)\n    }\n\n    const start = Date.now()\n    const req = await createLocalReq({}, payload)\n\n    try {\n      payload.logger.info({ msg: `Migrating down: ${migrationFile.name}` })\n      await initTransaction(req)\n      const db = await getTransaction(this, req)\n      await migrationFile.down({ db, payload, req })\n      payload.logger.info({\n        msg: `Migrated down:  ${migrationFile.name} (${Date.now() - start}ms)`,\n      })\n\n      const tableExists = await migrationTableExists(this, db)\n\n      if (tableExists) {\n        await payload.delete({","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/drizzle/src/migrateDown.ts#L20-L56","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before: migration file 0002_add_field was deleted from src/migrations but still in payload-migrations table\n// after: restore the file so names match\n//   git checkout HEAD -- src/migrations/0002_add_field.ts\n// then re-run:\n//   payload migrate:down","handlingStrategy":"validation","validationCode":"// Before running migrateDown, verify every DB migration has a local file\nimport { readMigrationFiles } from 'payload'\n\nconst files = await readMigrationFiles({ payload })\nconst { existingMigrations } = await getMigrations({ payload })\nconst fileNames = new Set(files.map(f => f.name))\nconst missing = existingMigrations.filter(m => !fileNames.has(m.name))\nif (missing.length) {\n  throw new Error(`Refusing to migrate down; local file missing for: ${missing.map(m => m.name).join(', ')}`)\n}\nawait payload.db.migrateDown()","typeGuard":"const isMigrationFile = (m: unknown): m is { name: string; up: Function; down: Function } =>\n  typeof m === 'object' && m !== null && typeof (m as any).name === 'string' &&\n  typeof (m as any).down === 'function'","tryCatchPattern":"try {\n  await payload.db.migrateDown()\n} catch (err) {\n  if (err instanceof Error && err.message.includes('not found locally')) {\n    payload.logger.error(`Migration drift detected. Restore missing files or run migrate:fresh on dev DB.`)\n  }\n  throw err\n}","preventionTips":["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."],"tags":["migrations","drizzle","database-drift","rollback"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}