Automattic/mongoose · error · MongooseError

Query has invalid `op`: "${this.op}"

Error message

Query has invalid `op`: "${this.op}"

What it means

exec() looks up the operation name in an internal op-to-thunk map covering find, findOne, findOneAndDelete, findOneAndUpdate, findOneAndReplace, updateOne, updateMany, deleteOne, deleteMany, replaceOne, count, countDocuments, distinct, estimatedDocumentCount and findOneAndRemove. If query.op holds anything else (including a typo or a manually assigned string), Mongoose throws listing the invalid op.

Source

Thrown at lib/query.js:4763

  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) + '...';
    }
    throw new MongooseError('Query was already executed: ' + str);
  }
  this._execCount++;

  const _this = this;
  return traceQuery(async function maybeTracedQueryExec() {
    let skipWrappedFunction = null;

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Use a valid op: 'find', 'findOne', 'findOneAndUpdate', 'findOneAndDelete', 'findOneAndReplace', 'updateOne', 'updateMany', 'deleteOne', 'deleteMany', 'replaceOne', 'countDocuments', 'distinct', 'estimatedDocumentCount'.
  2. Prefer not setting op manually — call the chained method (query.find(), query.updateOne(...)) which sets op correctly.
  3. If you passed the op to exec(), verify spelling and casing against the list above.

Example fix

// before
await query.exec('findById'); // throws: Query has invalid `op`

// after
await Model.findById(id).exec();
// or: query.find({ _id: id }); await query.exec('find');
Defensive patterns

Strategy: type-guard

Validate before calling

const VALID_OPS = new Set(['find','findOne','findOneAndUpdate','findOneAndDelete','findOneAndReplace','updateOne','updateMany','deleteOne','deleteMany','replaceOne','count','countDocuments','estimatedDocumentCount','distinct','findOneAndRemove']);
if (!VALID_OPS.has(op)) throw new Error(`Unknown op '${op}'`);

Type guard

const isValidOp = (op) => typeof op === 'string' && VALID_OPS.has(op);

Try / catch

try { await query.exec(op); } catch (err) { if (err instanceof mongoose.Error && /invalid `op`/.test(err.message)) { /* fix op name and retry */ } throw err; }

Prevention

When it happens

Trigger: `query.exec('findById')` (not a valid op string); `query.op = 'findone'; query.exec()`; exec('save') or other document-level names; stale plugins setting custom op values.

Common situations: Passing a method name that exists on Model but not as a Query op; typos in string ops; copying op names from older Mongoose versions where the list differed.

Related errors


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