Automattic/mongoose · error · MongooseError

Query.prototype.findOne() no longer accepts a callback

Error message

Query.prototype.findOne() no longer accepts a callback

What it means

Query.prototype.findOne() checks every declared parameter (conditions, projection, options) plus arguments[3] for functions and throws this MongooseError at construction time. Like all query helpers, findOne() has been promise-only since Mongoose 7, so any nodeback-style invocation such as Model.findOne(filter, cb) is rejected before a query runs.

Source

Thrown at lib/query.js:2834

 *     const query = Kitten.where({ color: 'white' });
 *     const kitten = await query.findOne();
 *
 * @param {object} [filter] mongodb selector
 * @param {object} [projection] optional fields to return
 * @param {object} [options] see [`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} this
 * @see findOne https://www.mongodb.com/docs/manual/reference/method/db.collection.findOne/
 * @see Query.select https://mongoosejs.com/docs/api/query.html#Query.prototype.select()
 * @api public
 */

Query.prototype.findOne = function(conditions, projection, options) {
  if (typeof conditions === 'function' ||
      typeof projection === 'function' ||
      typeof options === 'function' ||
      typeof arguments[3] === 'function') {
    throw new MongooseError('Query.prototype.findOne() no longer accepts a callback');
  }

  this.op = 'findOne';

  if (options) {
    this.setOptions(options);
  }

  if (projection) {
    this.select(projection);
  }

  if (canMerge(conditions)) {
    this.merge(conditions);

    prepareDiscriminatorCriteria(this);
  } else if (conditions != null) {
    this.error(new ObjectParameterError(conditions, 'filter', 'findOne'));

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Use async/await: const user = await Model.findOne({ email }) inside try/catch
  2. Or Model.findOne({ email }).then(user => ...).catch(err => ...)
  3. Sweep for findOne( calls containing 'err =>' and strip the callbacks

Example fix

// before
Model.findOne({ email }, (err, user) => {
  if (err) return next(err);
  if (!user) return res.status(401).end();
  res.json(user);
});

// after
try {
  const user = await Model.findOne({ email });
  if (!user) return res.status(401).end();
  res.json(user);
} catch (err) {
  next(err);
}
Defensive patterns

Strategy: validation

Validate before calling

function findOneSafe(model, conditions, projection, options) {
  if ([conditions, projection, options].some(v => typeof v === 'function')) {
    throw new Error('findOne() is promise-only in Mongoose 7+');
  }
  return model.findOne(conditions, projection, options);
}

Type guard

const isLegacyCallback = (v) => typeof v === 'function';

Try / catch

try {
  const user = await Model.findOne({ email });
} catch (err) {
  if (err?.message?.includes('no longer accepts a callback')) {
    // leftover callback signature at this call site
  }
  throw err;
}

Prevention

When it happens

Trigger: Model.findOne({ email }, (err, user) => {...}); Model.findOne({}, 'name', cb); a function accidentally passed as the projection, e.g. .findOne({}, buildProjection) instead of .findOne({}, buildProjection()).

Common situations: Legacy authentication middleware doing User.findOne(email, cb); upgrading to Mongoose 7/8; refactors that reordered arguments but left the callback in place.

Related errors


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