mongodb/node-mongodb-native · error · MongoInvalidArgumentError

Operation must be an object with an operation key

Error message

Operation must be an object with an operation key

What it means

Thrown as MongoInvalidArgumentError by BulkOperationBase.raw() (src/bulk/common.ts:1071) when the operation argument is null, undefined, or not an object. raw() dispatches on which operation key (insertOne/updateOne/etc.) is present, so it requires a non-null object containing at least one recognized operation key.

Solutions

  1. Inspect the operations array and ensure every element is a non-null object with exactly one of the recognized keys (insertOne, updateOne, updateMany, replaceOne, deleteOne, deleteMany).
  2. Filter out falsy entries before calling bulkWrite: ops.filter(Boolean).
  3. If a variable holds the inner payload, wrap it: ops.push({ insertOne: { document: doc } }).

Example fix

// before
await collection.bulkWrite([
  undefined,
  { insertOne: { document: { a: 1 } } }
]);

// after
await collection.bulkWrite(
  [
    undefined,
    { insertOne: { document: { a: 1 } } }
  ].filter((op): op is NonNullable<typeof op> => op != null)
);
Defensive patterns

Strategy: type-guard

Validate before calling

function isBulkOpObject(op: unknown): op is Record<string, unknown> {
  return op != null && typeof op === 'object' && !Array.isArray(op);
}

const cleaned = ops.filter(isBulkOpObject);
if (cleaned.length !== ops.length) {
  throw new Error('one or more bulk operations were null/non-object');
}
await coll.bulkWrite(cleaned);

Type guard

function isBulkOp(op: unknown): op is Record<string, unknown> {
  return op != null && typeof op === 'object' && !Array.isArray(op);
}

Try / catch

try {
  await coll.bulkWrite(ops);
} catch (e) {
  if (e instanceof MongoInvalidArgumentError && /must be an object/.test(e.message)) {
    await coll.bulkWrite(ops.filter((o): o is NonNullable<typeof o> => o != null && typeof o === 'object'));
  } else throw e;
}

Prevention

When it happens

Trigger: Calling collection.bulkWrite(ops) (which calls raw() per op) where one of the entries in the ops array is null, undefined, a primitive, or an array. Guarded by `if (op == null || typeof op !== 'object')` at src/bulk/common.ts:1070.

Common situations: A .map() that returns undefined for some elements, a filtered list that left a hole, JSON.parse of malformed input producing nulls, or accidentally passing the inner payload (e.g. op.insertOne) instead of the wrapper object { insertOne: ... }.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11). Data as JSON: /api/errors/66d5d9b5449f035e. Report an issue: GitHub.

Appendix: source

Thrown at src/bulk/common.ts:1071

   * ```
   */
  find(selector: Document): FindOperators {
    if (!selector) {
      throw new MongoInvalidArgumentError('Bulk find operation must specify a selector');
    }

    // Save a current selector
    this.s.currentOp = {
      selector: selector
    };

    return new FindOperators(this);
  }

  /** Specifies a raw operation to perform in the bulk write. */
  raw(op: AnyBulkWriteOperation): this {
    if (op == null || typeof op !== 'object') {
      throw new MongoInvalidArgumentError('Operation must be an object with an operation key');
    }
    if ('insertOne' in op) {
      const forceServerObjectId = this.shouldForceServerObjectId();
      const document =
        op.insertOne && op.insertOne.document == null
          ? // TODO(NODE-6003): remove support for omitting the `documents` subdocument in bulk inserts
            (op.insertOne as Document)
          : op.insertOne.document;

      maybeAddIdToDocuments(this.collection, document, { forceServerObjectId });

      return this.addToOperationsList(BatchType.INSERT, document);
    }

    if ('replaceOne' in op || 'updateOne' in op || 'updateMany' in op) {
      if ('replaceOne' in op) {
        if ('q' in op.replaceOne) {
          throw new MongoInvalidArgumentError('Raw operations are not allowed');

View on GitHub (pinned to dce7939f86)