Automattic/mongoose · error · Error

Must provide a filter object.

Error message

Must provide a filter object.

What it means

During Model.bulkWrite(), Mongoose casts each operation; castUpdateOne (lib/helpers/model/castBulkWrite.js) requires every updateOne op to carry both a filter and an update. A missing or falsy filter (undefined, null, omitted key, or a typo'd key like query or where) throws Error('Must provide a filter object.') before anything is sent to the server.

Source

Thrown at lib/helpers/model/castBulkWrite.js:108

    doc.$session(options.session);
  }
  const versionKey = model?.schema?.options?.versionKey;
  if (versionKey && doc[versionKey] == null) {
    doc[versionKey] = 0;
  }
  insertOne['document'] = doc;

  if (options.skipValidation || insertOne.skipValidation) {
    return insertOne;
  }

  await insertOne['document'].$validate();
  return insertOne;
};

module.exports.castUpdateOne = function castUpdateOne(originalModel, updateOne, options, now) {
  if (!updateOne['filter']) {
    throw new Error('Must provide a filter object.');
  }
  if (!updateOne['update']) {
    throw new Error('Must provide an update object.');
  }

  const model = decideModelByObject(originalModel, updateOne['filter']);
  const schema = model.schema;
  const strict = options.strict ?? model.schema.options.strict;

  const update = clone(updateOne['update']);

  _addDiscriminatorToObject(schema, updateOne['filter']);

  const doInitTimestamps = getTimestampsOpt(updateOne, options);

  if (model.schema.$timestamps != null && doInitTimestamps) {
    const createdAt = model.schema.$timestamps.createdAt;
    const updatedAt = model.schema.$timestamps.updatedAt;

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Ensure every updateOne op has a filter, typically { filter: { _id: row._id }, update: { $set: ... } }.
  2. Validate and normalize the ops array before calling bulkWrite (see validation code).
  3. If you meant to update many documents, use the updateMany op or Model.updateMany() with an explicit filter - never an accidentally empty one.

Example fix

// before
await Model.bulkWrite(rows.map(r => ({
  updateOne: { update: { $set: { status: r.status } } } // filter missing
})));

// after
await Model.bulkWrite(rows.map(r => ({
  updateOne: { filter: { _id: r._id }, update: { $set: { status: r.status } } }
})));
Defensive patterns

Strategy: validation

Validate before calling

function normalizeBulkOps(ops) {
  return ops.map(op => {
    if (op.updateOne != null && op.updateOne.filter == null) {
      throw new Error(`updateOne missing filter: ${JSON.stringify(op).slice(0, 200)}`);
    }
    return op;
  });
}
await Model.bulkWrite(normalizeBulkOps(ops));

Type guard

function isCompleteUpdateOne(op) {
  return op?.updateOne?.filter != null && op.updateOne.update != null;
}

Prevention

When it happens

Trigger: Model.bulkWrite([{ updateOne: { update: { $set: { status: 1 } } } } ]) with no filter key; dynamically built ops where filter is undefined for some rows; { updateOne: { filter: null, update } }; a refactor renamed the filter field.

Common situations: ETL scripts assembling bulk ops from CSV or API rows where some rows lack an id; partial spreads like { ...base, update } accidentally dropping filter; copying op shapes from updateMany examples.

Related errors


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