{"record":{"id":"6ff74878fe2eef47","repo":"Automattic/mongoose","slug":"model-cleanindexes-no-longer-accepts-a-callback","errorCode":null,"errorMessage":"Model.cleanIndexes() no longer accepts a callback","messagePattern":"Model\\.cleanIndexes\\(\\) no longer accepts a callback","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/model.js","lineNumber":1573,"sourceCode":"  return toDrop;\n}\n/**\n * Deletes all indexes that aren't defined in this model's schema. Used by\n * `syncIndexes()`.\n *\n * The returned promise resolves to a list of the dropped indexes' names as an array\n *\n * @param {object} [options]\n * @param {string[]} [options.toDrop] if specified, contains a list of index names to drop\n * @param {boolean} [options.hideIndexes=false] set to `true` to hide indexes instead of dropping. Requires MongoDB server 4.4 or higher\n * @return {Promise<string[]>} list of dropped or hidden index names\n * @api public\n */\n\nModel.cleanIndexes = async function cleanIndexes(options) {\n  _checkContext(this, 'cleanIndexes');\n  if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function') {\n    throw new MongooseError('Model.cleanIndexes() no longer accepts a callback');\n  }\n  const model = this;\n\n  if (Array.isArray(options?.toDrop)) {\n    const res = await _dropIndexes(options.toDrop, model, options);\n    return res;\n  }\n\n  const res = await model.diffIndexes();\n  return await _dropIndexes(res.toDrop, model, options);\n};\n\nasync function _dropIndexes(toDrop, model, options) {\n  if (toDrop.length === 0) {\n    return [];\n  }\n\n  const collection = model.$__collection;","sourceCodeStart":1555,"sourceCodeEnd":1591,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/model.js#L1555-L1591","documentation":"Model.cleanIndexes() drops (or hides, with `hideIndexes: true`) indexes that exist in MongoDB but not in the schema, returning the affected index names. Mongoose 7 removed its callback form, so a function in either argument slot throws before diffing starts.","triggerScenarios":"`User.cleanIndexes(cb)`; `User.cleanIndexes({ hideIndexes: true }, cb)`; teardown/maintenance scripts written against Mongoose 5/6.","commonSituations":"Apps upgrading mongoose majors while keeping old maintenance scripts; scripts that clean indexes for several models with a shared callback.","solutions":["Await it: `const dropped = await User.cleanIndexes();`","Pass a precomputed list if you orchestrate externally: `await User.cleanIndexes({ toDrop: ['name_1'] })`","Batch over models with Promise.all instead of callback counters"],"exampleFix":"// before\nUser.cleanIndexes(function(err, dropped) { ... });\n\n// after\nconst dropped = await User.cleanIndexes();","handlingStrategy":"validation","validationCode":"const isFn = (a) => typeof a === 'function';\nif (isFn(options)) throw new TypeError('cleanIndexes() is promise-only');\nconst dropped = await User.cleanIndexes(options);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Prefer syncIndexes({ hideIndexes: true }) over manual cleanIndexes scripts written in callback days","Run index maintenance through the same promise-based job runner as the rest of ops code"],"tags":["mongoose","callback-removed","promise","indexes","migration"],"backgroundTag":"mongoose-callback-removed","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}