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

FindOneAndReplaceOperation's constructor (src/operations/find_and_modify.ts:240) calls hasAtomicOperators(replacement) and throws a MongoInvalidArgumentError if true. A replacement document must be a plain document — it fully substitutes the matched document — so update operators like $set, $inc, $unset are forbidden. This is a common confusion between findOneAndReplace and findOneAndUpdate.

Source

Thrown at src/operations/find_and_modify.ts:240

/** @internal */
export class FindOneAndReplaceOperation extends FindAndModifyOperation {
  private replacement: Document;
  constructor(
    collection: Collection,
    filter: Document,
    replacement: Document,
    options: FindOneAndReplaceOptions
  ) {
    if (filter == null || typeof filter !== 'object') {
      throw new MongoInvalidArgumentError('Argument "filter" must be an object');
    }

    if (replacement == null || typeof replacement !== 'object') {
      throw new MongoInvalidArgumentError('Argument "replacement" must be an object');
    }

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

    super(collection, filter, options);
    this.replacement = replacement;
  }

  override buildCommandDocument(
    connection: Connection,
    session?: ClientSession
  ): Document & FindAndModifyCmdBase {
    const document = super.buildCommandDocument(connection, session);
    document.update = this.replacement;
    configureFindAndModifyCmdBaseUpdateOpts(document, this.options);
    return document;
  }
}

/** @internal */

View on GitHub (pinned to 3366c21a63)

Solutions

  1. Use collection.findOneAndUpdate(filter, { $set: { ... } }) when you need atomic operators.
  2. If you truly want findOneAndReplace, pass a full replacement document without any $ operators.
  3. Audit the document to ensure no keys start with '$' before calling findOneAndReplace.

Example fix

// before
await collection.findOneAndReplace({ _id }, { $set: { status: 'done' } });

// after
await collection.findOneAndUpdate({ _id }, { $set: { status: 'done' } });
// or full replace:
await collection.findOneAndReplace({ _id }, { status: 'done', createdAt: new Date() });
Defensive patterns

Strategy: validation

Validate before calling

function hasAtomicOperators(doc: Record<string, unknown>): boolean {
  return Object.keys(doc).some(k => k.startsWith('$'));
}
if (hasAtomicOperators(replacement)) {
  throw new TypeError('Use findOneAndUpdate for atomic operators, not findOneAndReplace');
}

Type guard

const isAtomicUpdate = (doc: Record<string, unknown>): boolean =>
  Object.keys(doc).some(k => k.startsWith('$'));

Prevention

When it happens

Trigger: Calling collection.findOneAndReplace(filter, { $set: { field: value } }) or any replacement containing operators prefixed with $.

Common situations: Developers defaulting to $set out of habit and reaching for findOneAndReplace instead of findOneAndUpdate, or copy-pasting an update document into a replace call.

Related errors


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