mongodb/node-mongodb-native · error · MongoInvalidArgumentError

Replacement document must not use atomic operators

Error message

Replacement document must not use atomic operators

What it means

Thrown by FindOperators.replaceOne() when the replacement document contains atomic operators. A replacement document must be a plain object whose fields replace the existing document wholesale; mixing in $set/$inc etc. is illegal because the server would reject the command. hasAtomicOperators() returns true when the first key starts with '$'.

Source

Thrown at src/bulk/common.ts:749

  }

  /** Add a single update operation to the bulk operation */
  updateOne(updateDocument: Document | Document[]): BulkOperationBase {
    if (!hasAtomicOperators(updateDocument, this.bulkOperation.bsonOptions)) {
      throw new MongoInvalidArgumentError('Update document requires atomic operators');
    }

    const currentOp = buildCurrentOp(this.bulkOperation);
    return this.bulkOperation.addToOperationsList(
      BatchType.UPDATE,
      makeUpdateStatement(currentOp.selector, updateDocument, { ...currentOp, multi: false })
    );
  }

  /** Add a replace one operation to the bulk operation */
  replaceOne(replacement: Document): BulkOperationBase {
    if (hasAtomicOperators(replacement)) {
      throw new MongoInvalidArgumentError('Replacement document must not use atomic operators');
    }

    const currentOp = buildCurrentOp(this.bulkOperation);
    return this.bulkOperation.addToOperationsList(
      BatchType.UPDATE,
      makeUpdateStatement(currentOp.selector, replacement, { ...currentOp, multi: false })
    );
  }

  /** Add a delete one operation to the bulk operation */
  deleteOne(): BulkOperationBase {
    const currentOp = buildCurrentOp(this.bulkOperation);
    return this.bulkOperation.addToOperationsList(
      BatchType.DELETE,
      makeDeleteStatement(currentOp.selector, { ...currentOp, limit: 1 })
    );
  }

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Strip the atomic operator and pass a plain replacement: { $set: { a: 1 } } -> { a: 1 }.
  2. If you actually want partial update semantics, switch to updateOne/updateMany instead of replaceOne.

Example fix

// before
bulk.find({ _id: 1 }).replaceOne({ $set: { name: 'alice' } });

// after
bulk.find({ _id: 1 }).replaceOne({ name: 'alice' });
Defensive patterns

Strategy: validation

Validate before calling

function isPlainReplacement(doc) {
  return doc != null && typeof doc === 'object' &&
    Object.keys(doc).every(k => k[0] !== '$');
}

Type guard

function isReplacementDoc(doc) {
  return (
    doc != null &&
    typeof doc === 'object' &&
    !Array.isArray(doc) &&
    (Object.keys(doc).length === 0 || Object.keys(doc)[0][0] !== '$')
  );
}

Try / catch

try {
  bulk.find(filter).replaceOne(rep);
} catch (e) {
  if (e instanceof MongoInvalidArgumentError && /must not use atomic/.test(e.message)) {
    // strip $-wrapper or route to updateOne
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling bulk.find(filter).replaceOne({ $set: {...} }) or any replaceOne where the first key is a $-operator. Copy-pasting an update document into a replaceOne call site.

Common situations: Refactoring between updateOne and replaceOne without adjusting the document shape; building documents generically and routing them to the wrong method.

Related errors


AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04). Data as JSON: /data/errors/c63ddc841faa3019.json. Report an issue: GitHub.