Automattic/mongoose · error · MongooseError

Model.findOne() no longer accepts a callback

Error message

Model.findOne() no longer accepts a callback

What it means

Model.findOne(conditions, projection, options) returns a Query resolving to the first match or null. Callbacks were removed in Mongoose 7; a function in any of the first three positional slots throws immediately inside findOne itself.

Source

Thrown at lib/model.js:2204

 *     // Model.findOne() no longer accepts a callback
 *
 *     // Select only the adventures name and length
 *     await Adventure.findOne({ country: 'Croatia' }, 'name length').exec();
 *
 * @param {object} [conditions]
 * @param {object|string|string[]} [projection] optional fields to return, see [`Query.prototype.select()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.select())
 * @param {object} [options] optional see [`Query.prototype.setOptions()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.setOptions())
 * @param {boolean} [options.translateAliases=null] If set to `true`, translates any schema-defined aliases in `filter`, `projection`, `update`, and `distinct`. Throws an error if there are any conflicts where both alias and raw property are defined on the same object.
 * @return {Query}
 * @see field selection https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
 * @see lean queries https://mongoosejs.com/docs/tutorials/lean.html
 * @api public
 */

Model.findOne = function findOne(conditions, projection, options) {
  _checkContext(this, 'findOne');
  if (typeof arguments[0] === 'function' || typeof arguments[1] === 'function' || typeof arguments[2] === 'function') {
    throw new MongooseError('Model.findOne() no longer accepts a callback');
  }

  const mq = new this.Query({}, {}, this, this.$__collection);
  mq.select(projection);
  mq.setOptions(options);

  return mq.findOne(conditions);
};

/**
 * Estimates the number of documents in the MongoDB collection. Faster than
 * using `countDocuments()` for large collections because
 * `estimatedDocumentCount()` uses collection metadata rather than scanning
 * the entire collection.
 *
 * #### Example:
 *
 *     const numAdventures = await Adventure.estimatedDocumentCount();

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Await it: `const user = await User.findOne({ email });`
  2. Use `.orFail()` when a missing document should reject instead of resolving null
  3. Sweep `findOne\(` call sites for function arguments during migration

Example fix

// before
User.findOne({ email }, (err, user) => { if (err) throw err; ... });

// after
const user = await User.findOne({ email });
Defensive patterns

Strategy: validation

Validate before calling

const isFn = (a) => typeof a === 'function';
if ([conditions, projection, options].some(isFn)) {
  throw new TypeError('findOne() is promise-only');
}
const user = await User.findOne(conditions, projection, options);

Prevention

When it happens

Trigger: `User.findOne({ email }, (err, user) => ...)`; login/lookup handlers written callback-style; passing a callback in place of `options`.

Common situations: Authentication flows (`findOne({ email })`) ported from old tutorials; the upgrade from mongoose 6 to 7+.

Related errors


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/d65b556518f97639. Report an issue: GitHub.