Automattic/mongoose · error · MongooseError

Query must have `op` before executing

Error message

Query must have `op` before executing

What it means

Query.prototype.exec() requires that the query already knows which operation to run; `this.op` is set by find(), findOne(), updateOne(), countDocuments(), etc. If exec() runs on a query with no operation, Mongoose throws this error because there is nothing meaningful to send to the server.

Source

Thrown at lib/query.js:4755

 *     const promise = query.exec('update');
 *
 * @param {string|Function} [operation]
 * @return {Promise}
 * @api public
 */

Query.prototype.exec = async function exec(op) {
  if (typeof op === 'function' || (arguments.length >= 2 && typeof arguments[1] === 'function')) {
    throw new MongooseError('Query.prototype.exec() no longer accepts a callback');
  }

  this._validateOp();
  if (typeof op === 'string') {
    this.op = op;
  }

  if (this.op == null) {
    throw new MongooseError('Query must have `op` before executing');
  }
  if (this.model == null) {
    throw new MongooseError('Query must have an associated model before executing');
  }

  const thunk = opToThunk.get(this.op);
  if (!thunk) {
    throw new MongooseError('Query has invalid `op`: "' + this.op + '"');
  }

  if (this.options?.sort && typeof this.options.sort === 'object' && Object.hasOwn(this.options.sort, '')) {
    throw new MongooseError('Invalid field "" passed to sort()');
  }

  if (this._execCount > 0) {
    let str = this.toString();
    if (str.length > 60) {
      str = str.slice(0, 60) + '...';

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Add the operation before exec: `Model.find({ name: 'x' }).exec()` or `Model.where('name', 'x').find().exec()`.
  2. Or pass the op string to exec if the query has conditions but no op: `query.exec('find')`.
  3. Audit builder functions to guarantee every code path ends in find()/findOne()/updateOne()/etc.

Example fix

// before
const docs = await Model.where('age').gte(18).exec(); // throws: Query must have `op` before executing

// after
const docs = await Model.where('age').gte(18).find().exec();
Defensive patterns

Strategy: validation

Validate before calling

function execQuery(query) {
  if (query.op == null) query.find(); // or throw: conditions without an operation
  return query.exec();
}

Type guard

const queryHasOp = (q) => q.op != null;

Try / catch

try { await q.exec(); } catch (err) { if (err instanceof mongoose.Error && /must have `op`/.test(err.message)) { q.find(); return q.exec(); } throw err; }

Prevention

When it happens

Trigger: `Model.where('name', 'x').exec()` (where() only adds a condition, it does not set an op); `new Model.Query().exec()`; exec('') with an empty string; building conditions via query helpers and forgetting the terminal find/findOne call.

Common situations: Query-builder helpers that chain where()/sort()/limit() and accidentally return before calling find(); dynamically skipping the operation-setting step because of a falsy branch; copy-paste that drops the `.find()`.

Related errors


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