Automattic/mongoose · error · MongooseError

Document.prototype.validate() no longer accepts a callback

Error message

Document.prototype.validate() no longer accepts a callback

What it means

Mongoose 7 removed callback support from every API, including Document#validate(). If any of the validate() arguments is a function (as pathsToValidate, as options, or a third argument), validate() throws immediately instead of starting validation.

Source

Thrown at lib/document.js:2741

 *
 * #### Example:
 *
 *     await doc.validate({ validateModifiedOnly: false, pathsToSkip: ['name', 'email']});
 *
 * @param {Array|string} [pathsToValidate] list of paths to validate. If set, Mongoose will validate only the modified paths that are in the given list.
 * @param {object} [options] internal options
 * @param {boolean} [options.validateModifiedOnly=false] if `true` mongoose validates only modified paths.
 * @param {Array|string} [options.pathsToSkip] list of paths to skip. If set, Mongoose will validate every modified path that is not in this list.
 * @param {boolean|object} [options.middleware=true] set to `false` to skip all user-defined middleware
 * @param {boolean} [options.middleware.pre=true] set to `false` to skip only pre hooks
 * @param {boolean} [options.middleware.post=true] set to `false` to skip only post hooks
 * @return {Promise} Returns a Promise.
 * @api public
 */

Document.prototype.validate = async function validate(pathsToValidate, options) {
  if (typeof pathsToValidate === 'function' || typeof options === 'function' || typeof arguments[2] === 'function') {
    throw new MongooseError('Document.prototype.validate() no longer accepts a callback');
  }
  this.$op = 'validate';

  if (arguments.length === 1) {
    if (typeof arguments[0] === 'object' && !Array.isArray(arguments[0])) {
      options = arguments[0];
      pathsToValidate = null;
    }
  }
  if (options && typeof options.pathsToSkip === 'string') {
    const isOnePathOnly = options.pathsToSkip.indexOf(' ') === -1;
    options.pathsToSkip = isOnePathOnly ? [options.pathsToSkip] : options.pathsToSkip.split(' ');
  }
  const _skipParallelValidateCheck = options?._skipParallelValidateCheck;

  if (this.$isSubdocument != null) {
    // Skip parallel validate check for subdocuments
  } else if (this.$__.validating && !_skipParallelValidateCheck) {

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Await the promise: `try { await doc.validate(); } catch (err) { ... }`
  2. Or chain: `doc.validate().then(next).catch(errHandler)`
  3. For synchronous checks use `doc.validateSync()` (returns a ValidationError)
  4. Grep the codebase for `validate(function` and `validate(cb` patterns and migrate them all

Example fix

// before
doc.validate(function(err) { if (err) return next(err); next(); });

// after
try {
  await doc.validate();
  next();
} catch (err) {
  next(err);
}
Defensive patterns

Strategy: validation

Validate before calling

// Wrapper that refuses callback-style usage before it reaches mongoose
function validateDoc(doc, ...args) {
  if (args.some(a => typeof a === 'function')) {
    throw new TypeError('Callbacks are not supported; await the returned promise instead');
  }
  return doc.validate(...args);
}

Type guard

const isNotCallback = (...args) => args.every(a => typeof a !== 'function');

Prevention

When it happens

Trigger: Legacy Mongoose <= 6 code: `doc.validate(function(err) {...})`, `doc.validate(['name'], cb)`, or `doc.validate({ validateModifiedOnly: true }, cb)`.

Common situations: Upgrading a Mongoose 6 codebase to 7/8; copied tutorial snippets using node-style callbacks; wrapper libraries that accept callbacks and forward them verbatim.

Related errors


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