Automattic/mongoose · error · MongooseError

Options must be an object, got "${options}"

Error message

Options must be an object, got "${options}"

What it means

Query.prototype.setOptions() requires its options argument to be an object; null/undefined return early, and any other primitive (string, number, boolean) throws a MongooseError with the offending value interpolated into the message. Because every query helper that takes an options parameter (findOne, countDocuments, deleteOne, findOneAndUpdate, etc.) delegates to setOptions(), the error usually surfaces from the outer method call rather than an explicit setOptions() call.

Source

Thrown at lib/query.js:1743

 */

Query.prototype.setOptions = function(options, overwrite) {
  // overwrite is only for internal use
  if (overwrite) {
    // ensure that _mongooseOptions & options are two different objects
    this._mongooseOptions = (options && clone(options)) || {};
    this.options = options || {};

    if ('populate' in options) {
      this.populate(this._mongooseOptions);
    }
    return this;
  }
  if (options == null) {
    return this;
  }
  if (typeof options !== 'object') {
    throw new MongooseError('Options must be an object, got "' + options + '"');
  }

  options = Object.assign({}, options);

  if (Array.isArray(options.populate)) {
    const populate = options.populate;
    delete options.populate;
    const _numPopulate = populate.length;
    for (let i = 0; i < _numPopulate; ++i) {
      this.populate(populate[i]);
    }
  }

  if ('cloneUpdate' in options) {
    this._mongooseOptions.cloneUpdate = options.cloneUpdate;
    delete options.cloneUpdate;
  }
  if ('defaults' in options) {

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Pass a plain object: .findOne(filter, projection, { lean: true })
  2. JSON.parse() any serialized options string before passing it
  3. Re-check argument order — a primitive in the options slot usually means misaligned arguments (frequently a leftover callback or an extra leading parameter)

Example fix

// before
const doc = await Model.findOne({}, null, process.env.FIND_OPTS); // FIND_OPTS is the string '{"lean":true}'

// after
const doc = await Model.findOne({}, null, JSON.parse(process.env.FIND_OPTS));
Defensive patterns

Strategy: type-guard

Validate before calling

function toOptions(raw) {
  if (raw == null) return undefined;
  if (typeof raw === 'string') {
    try { return JSON.parse(raw); } catch { return undefined; }
  }
  return typeof raw === 'object' ? raw : undefined;
}
const doc = await Model.findOne({}, null, toOptions(maybeOptions));

Type guard

const isOptionsObject = (v) => v == null || (typeof v === 'object' && v !== null);

Prevention

When it happens

Trigger: .findOne(filter, projection, 'lean') instead of { lean: true }; .countDocuments({}, 10); .deleteOne({}, true); .setOptions('limit=5'); passing an unparsed JSON string pulled from an env var, cache, or message queue.

Common situations: Options serialized as strings (env vars, Redis, job queues) that were never JSON.parsed; argument positions shifted after removing a legacy callback during a Mongoose 7 upgrade; passing a URL query string instead of a parsed object.

Related errors


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