Automattic/mongoose · error · MongooseError
Query.prototype.findOneAndReplace() no longer accepts a call
Error message
Query.prototype.findOneAndReplace() no longer accepts a callback
What it means
Query.prototype.findOneAndReplace(filter, replacement, options) throws when filter, replacement, or options is a function, or when a fifth positional argument (arguments[4]) is a function. Note the guard checks arguments[4] rather than arguments[3], so a callback passed as a fourth argument is not caught by this specific check — functions in the named parameter slots are what reliably trigger it. This is part of the Mongoose 7 removal of callback execution.
Source
Thrown at lib/query.js:3789
* @param {boolean} [options.new=false] By default, `findOneAndUpdate()` returns the document as it was **before** `update` was applied. If you set `new: true`, `findOneAndUpdate()` will instead give you the object after `update` was applied. **Deprecated:** Use `returnDocument: 'after'` instead of `new: true`, or `returnDocument: 'before'` instead of `new: false`.
* @param {object} [options.lean] if truthy, mongoose will return the document as a plain JavaScript object rather than a mongoose document. See [`Query.lean()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.lean()) and [the Mongoose lean tutorial](https://mongoosejs.com/docs/tutorials/lean.html).
* @param {ClientSession} [options.session=null] The session associated with this query. See [transactions docs](https://mongoosejs.com/docs/transactions.html).
* @param {boolean|'throw'} [options.strict] overwrites the schema's [strict mode option](https://mongoosejs.com/docs/guide.html#strict)
* @param {boolean} [options.timestamps=null] If set to `false` and [schema-level timestamps](https://mongoosejs.com/docs/guide.html#timestamps) are enabled, skip timestamps for this update. Note that this allows you to overwrite timestamps. Does nothing if schema-level timestamps are not set.
* @param {boolean} [options.returnOriginal=null] An alias for the `new` option. `returnOriginal: false` is equivalent to `new: true`. **Deprecated:** Use `returnDocument: 'after'` instead of `returnOriginal: false`, or `returnDocument: 'before'` instead of `returnOriginal: true`.
* @param {'before'|'after'} [options.returnDocument='before'] Has two possible values, `'before'` and `'after'`. By default, it will return the document before the update was applied.
* @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.
* @param {boolean} [options.requireFilter=false] If true, throws an error if the filter is empty (`{}`)
* @return {Query} this
* @api public
*/
Query.prototype.findOneAndReplace = function(filter, replacement, options) {
if (typeof filter === 'function' ||
typeof replacement === 'function' ||
typeof options === 'function' ||
typeof arguments[4] === 'function') {
throw new MongooseError('Query.prototype.findOneAndReplace() no longer accepts a callback');
}
this.op = 'findOneAndReplace';
this._validate();
if (canMerge(filter)) {
this.merge(filter);
} else if (filter != null) {
this.error(
new ObjectParameterError(filter, 'filter', 'findOneAndReplace')
);
}
if (replacement != null) {
this._mergeUpdate(replacement);
}
options = options || {};View on GitHub (pinned to 49cdab0136)
Solutions
- Use await: const doc = await Model.findOneAndReplace(filter, replacement, { returnDocument: 'after' })
- Wrap in try/catch; the promise resolves null when no document matched
- Strip callbacks from all findOneAndReplace call sites when migrating to mongoose 7+
Example fix
// before
Model.findOneAndReplace({ key: 'theme' }, { key: 'theme', color: 'blue' }, (err, doc) => {
if (err) return next(err);
res.json(doc);
});
// after
try {
const doc = await Model.findOneAndReplace(
{ key: 'theme' },
{ key: 'theme', color: 'blue' },
{ returnDocument: 'after' }
);
res.json(doc);
} catch (err) {
next(err);
} Defensive patterns
Strategy: validation
Validate before calling
function findOneAndReplaceSafe(model, filter, replacement, options) {
if ([filter, replacement, options].some(v => typeof v === 'function')) {
throw new Error('findOneAndReplace() is promise-only in Mongoose 7+');
}
return model.findOneAndReplace(filter, replacement, options);
} Type guard
const isLegacyCallback = (v) => typeof v === 'function';
Try / catch
try {
const doc = await Model.findOneAndReplace(filter, replacement, { returnDocument: 'after' });
} catch (err) {
if (err?.message?.includes('no longer accepts a callback')) {
// fix the findOneAndReplace call site that still passes a callback
}
throw err;
} Prevention
- Await findOneAndReplace with an explicit options object
- Remember the replacement must be a plain document, not an update operator object
- Note: a 4th-argument function is not caught by this specific guard — do not rely on positional extras
When it happens
Trigger: Model.findOneAndReplace(filter, replacement, cb) with the callback in the options slot; .findOneAndReplace(filter, cb) with the callback in the replacement slot; legacy code passing a fifth-argument callback.
Common situations: Document-replacement flows (config documents, upsert-by-replace) written for Mongoose 6; major upgrades; legacy wrappers that append callbacks positionally.
Related errors
- Model.findOneAndReplace() no longer accepts a callback
- Query.prototype.find() no longer accepts a callback
- Query.prototype.findOne() no longer accepts a callback
- Query.prototype.estimatedDocumentCount() no longer accepts a
- Query.prototype.countDocuments() no longer accepts a callbac
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/3a81ee70b325f026.
Report an issue: GitHub.