Automattic/mongoose · error · MongooseError

Model.findById() no longer accepts a callback

Error message

Model.findById() no longer accepts a callback

What it means

Model.findById(id, projection, options) is sugar for findOne({ _id: id }); Mongoose 7 removed callbacks, so passing a function in any of the first three argument slots throws before the findOne delegation happens. Note a callback passed as `id` itself also triggers this.

Source

Thrown at lib/model.js:2166

 *     await Adventure.findById(id).exec();
 *
 *     // select only the adventures name and length
 *     await Adventure.findById(id, 'name length').exec();
 *
 * @param {any} id value of `_id` to query by
 * @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())
 * @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
 * @see findById in Mongoose https://masteringjs.io/tutorials/mongoose/find-by-id
 * @api public
 */

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

  return this.findOne({ _id: id }, projection, options);
};

/**
 * Finds one document.
 *
 * The `conditions` are cast to their respective SchemaTypes before the command is sent.
 *
 * *Note:* `conditions` is optional, and if `conditions` is null or undefined,
 * mongoose will send an empty `findOne` command to MongoDB, which will return
 * an arbitrary document. If you're querying by `_id`, use `findById()` instead.
 *
 * #### Example:
 *
 *     // Find one adventure whose `country` is 'Croatia', otherwise `null`
 *     await Adventure.findOne({ country: 'Croatia' }).exec();

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Await it: `const user = await User.findById(id);`
  2. Handle not-found explicitly (result is null when missing)
  3. Migrate route handlers in bulk — search `findById\(` with a following function argument

Example fix

// before
User.findById(req.params.id, (err, user) => {
  if (err) return next(err);
  res.json(user);
});

// after
const user = await User.findById(req.params.id);
res.json(user);
Defensive patterns

Strategy: validation

Validate before calling

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

Prevention

When it happens

Trigger: `User.findById(id, (err, user) => ...)`; `User.findById(cb)`; older route handlers from Express + Mongoose 5/6 tutorials.

Common situations: Classic `GET /users/:id` handlers copied from pre-2023 tutorials; the most-hit call site family after find() during major upgrades.

Related errors


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