mongodb/node-mongodb-native · error · MongoInvalidArgumentError

Replacement document must not contain atomic operators

Error message

Replacement document must not contain atomic operators

What it means

Thrown by ReplaceOneOperation's constructor when the replacement argument passed to collection.replaceOne() DOES contain atomic operators. replaceOne performs a full-document replacement and must not use $set/$inc/etc.; the replacement is a plain document with the new field values. This is the inverse check from updateOne. MongoInvalidArgumentError.

Source

Thrown at src/operations/update.ts:235

  upsert?: boolean;
  /** Map of parameter names and values that can be accessed using $$var (requires MongoDB 5.0). */
  let?: Document;
  /** Specifies the sort order for the documents matched by the filter. */
  sort?: Sort;
}

/** @internal */
export class ReplaceOneOperation extends UpdateOperation {
  constructor(
    ns: MongoDBCollectionNamespace,
    filter: Document,
    replacement: Document,
    options: ReplaceOptions
  ) {
    super(ns, [makeUpdateStatement(filter, replacement, { ...options, multi: false })], options);

    if (hasAtomicOperators(replacement)) {
      throw new MongoInvalidArgumentError('Replacement document must not contain atomic operators');
    }
  }

  override handleOk(
    response: InstanceType<typeof this.SERVER_COMMAND_RESPONSE_TYPE>
  ): UpdateResult {
    const res = super.handleOk(response);

    // @ts-expect-error Explain typing is broken
    if (this.explain != null) return res;
    if (res.code) throw new MongoServerError(res);
    if (res.writeErrors) throw new MongoServerError(res.writeErrors[0]);

    return {
      acknowledged: this.writeConcern?.w !== 0,
      modifiedCount: res.nModified ?? res.n,
      upsertedId:
        Array.isArray(res.upserted) && res.upserted.length > 0 ? res.upserted[0]._id : null,

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Remove $ operators: pass the full target document, e.g. replaceOne(filter, { name: 'x', status: 1 }).
  2. If you actually want partial mutation with operators, use collection.updateOne() instead of replaceOne().
  3. Remember: replaceOne = whole document; updateOne = operators (or pipeline).
  4. Audit shared write-helper code that branches between the two.

Example fix

// before
await collection.replaceOne({ _id: 1 }, { $set: { name: 'x' } });
// after - choose one:
await collection.replaceOne({ _id: 1 }, { name: 'x' }); // full replace
// or
await collection.updateOne({ _id: 1 }, { $set: { name: 'x' } }); // partial update
Defensive patterns

Strategy: validation

Validate before calling

function isReplacementDoc(doc) {
  return doc != null && typeof doc === 'object' && !Array.isArray(doc) &&
    !Object.keys(doc).some(k => k.startsWith('$'));
}
if (!isReplacementDoc(replacement)) throw new Error('replaceOne replacement must not use $ operators');
await collection.replaceOne(filter, replacement);

Type guard

function isReplacementDoc(doc): doc is Record<string, unknown> {
  return doc != null && typeof doc === 'object' && !Array.isArray(doc) &&
    !Object.keys(doc).some(k => k.startsWith('$'));
}

Prevention

When it happens

Trigger: collection.replaceOne(filter, { $set: { ... } }) - the caller used replacement semantics but supplied update-operator syntax. Also happens when copy-pasting an updateOne call and only changing the method name.

Common situations: Renaming updateOne to replaceOne without removing the operators; mixing up the two operations' contracts; building a generic helper that assumes operators are always wanted.

Related errors


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