Automattic/mongoose · error · MongooseError
Model.exists() no longer accepts a callback
Error message
Model.exists() no longer accepts a callback
What it means
Model.exists() was callback-capable in Mongoose 5/6 (exists(filter, options, callback)); in Mongoose 7 it returns a lean Query/promise only. Calling it with a function in the third argument position throws this MongooseError so legacy three-argument calls fail loudly instead of never invoking the callback.
Source
Thrown at lib/model.js:950
* await Character.deleteMany({});
* await Character.create({ name: 'Jean-Luc Picard' });
*
* await Character.exists({ name: /picard/i }); // { _id: ... }
* await Character.exists({ name: /riker/i }); // null
*
* This function triggers the following middleware.
*
* - `findOne()`
*
* @param {object} filter
* @param {object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
* @return {Query}
*/
Model.exists = function exists(filter, options) {
_checkContext(this, 'exists');
if (typeof arguments[2] === 'function') {
throw new MongooseError('Model.exists() no longer accepts a callback');
}
const query = this.findOne(filter).
select({ _id: 1 }).
lean().
setOptions(options);
return query;
};
/**
* Adds a discriminator type.
*
* #### Example:
*
* function BaseSchema() {
* Schema.apply(this, arguments);
*View on GitHub (pinned to 49cdab0136)
Solutions
- Use await: const found = await Model.exists({ email }) — resolves to a lean { _id } document or null
- Wrap legacy callbacks at the boundary: Model.exists(f, o).then(r => cb(null, !!r), cb)
- Sweep for `.exists(` calls with a trailing function argument during the migration
Example fix
// before
User.exists({ email }, null, function (err, exists) {
if (exists) return res.status(409).end();
res.end();
});
// after
const exists = await User.exists({ email });
if (exists) return res.status(409).end();
res.end(); Defensive patterns
Strategy: validation
Validate before calling
// Normalize legacy exists() signatures before calling
function exists(model, filter, options) {
if (typeof options === 'function' || typeof arguments[3] === 'function') {
throw new Error('exists() is promise-based — await it instead of passing a callback');
}
return model.exists(filter, options);
} Type guard
const isCallback = (x) => typeof x === 'function';
Try / catch
try {
const found = await Model.exists({ email });
} catch (err) {
if (err instanceof mongoose.MongooseError && /no longer accepts a callback/.test(err.message)) {
// remove the trailing callback argument and await the query
} else throw err;
} Prevention
- Use `await Model.exists(filter)` — it resolves to a lean { _id } doc or null
- During Mongoose 7 upgrades, sweep every exists() call for trailing functions
- Keep duplicate-check helpers promise-based and let route handlers await them
When it happens
Trigger: Model.exists({ email }, null, function (err, ok) { ... }) or Model.exists({ email }, {}, cb) on Mongoose 7+ — the classic duplicate-check route handler left un-migrated.
Common situations: Mongoose 6-to-7 upgrades; signup/endpoints checking for existing records; automated migrations that stripped most callbacks but missed the three-argument exists() form.
Related errors
- Model.prototype.save() no longer accepts a callback
- Model.prototype.deleteOne() no longer accepts a callback
- Document.prototype.validate() no longer accepts a callback
- Model.findByIdAndUpdate() no longer accepts a callback
- Model.findOneAndDelete() no longer accepts a callback
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/5c45816e183f7bd9.
Report an issue: GitHub.