Automattic/mongoose · error · MongooseError

Model.find() no longer accepts a callback

Error message

Model.find() no longer accepts a callback

What it means

Model.find(conditions, projection, options) is the core query static; Mongoose 7 removed its callback form, so a function in any of the first four argument slots throws synchronously and no Query is returned. The result comes from awaiting the Query or calling .exec().

Solutions

  1. Await the query: `const users = await User.find({ age: { $gte: 18 } }, 'name');`
  2. Or `User.find({}).exec().then(...)`
  3. Automate the sweep: grep for `\.find\(.*=>` and `\.find\([^)]*,\s*function` and migrate all matches in one pass

Example fix

// before
User.find({ role: 'admin' }, (err, admins) => { if (err) throw err; ... });

// after
const admins = await User.find({ role: 'admin' });
Defensive patterns

Strategy: validation

Validate before calling

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

Prevention

When it happens

Trigger: `User.find({ age: { $gte: 18 } }, 'name', (err, docs) => ...)`; `User.find(cb)`; any of the 4 positional slots receiving a function.

Common situations: The single most common hit when upgrading mongoose 6 to 7+: find() calls are ubiquitous; old tutorials and generated CRUD code are callback-based.

Related errors


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

Appendix: source

Thrown at lib/model.js:2126

 *     await MyModel.find({ name: /john/i }, 'name friends').exec();
 *
 *     // passing options
 *     await MyModel.find({ name: /john/i }, null, { skip: 10 }).exec();
 *
 * @param {object|ObjectId} filter
 * @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 query casting https://mongoosejs.com/docs/tutorials/query_casting.html
 * @api public
 */

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

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

  return mq.find(conditions);
};

/**
 * Finds a single document by its _id field. `findById(id)` is equivalent to `findOne({ _id: id })`.
 *
 * The `id` is cast based on the Schema before sending the command.
 *
 * This function triggers the following middleware.
 *
 * - `findOne()`
 *

View on GitHub (pinned to 49cdab0136)