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
- Use a valid op: 'find', 'findOne', 'findOneAndUpdate', 'findOneAndDelete', 'findOneAndReplace', 'updateOne', 'updateMany', 'deleteOne', 'deleteMany', 'replaceOne', 'countDocuments', 'distinct', 'estimatedDocumentCount'.
- Prefer not setting op manually — call the chained method (query.find(), query.updateOne(...)) which sets op correctly.
- 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
- Avoid setting query.op manually; call the chained method instead.
- Keep op strings in constants instead of free-form literals.
- Note findById is a Model method, not a Query op string.
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
- Query must have `op` before executing
- Query must have an associated model before executing
- Invalid addFields() argument. Must be an object
- Query filter must be an object, got an array ${util.inspect(
- Cast to Array failed for value "${value}" at path "${path}"
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/4113f1e13259dda7.
Report an issue: GitHub.