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
- Use collection.findOneAndUpdate(filter, { $set: { ... } }) when you need atomic operators.
- If you truly want findOneAndReplace, pass a full replacement document without any $ operators.
- 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
- Use findOneAndUpdate for $set/$inc/$push; use findOneAndReplace only for full-document replacement.
- Scan replacement keys for a leading '$' before calling findOneAndReplace.
- Document the distinction in your codebase to prevent copy-paste mistakes.
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
- Argument "filter" must be an object
- Argument "replacement" must be an object
- Argument "update" must be an object
- Query filter must be a plain object or ObjectId
- Client bulk write update models must only contain atomic mod
AI-assisted analysis of mongodb/node-mongodb-native@3366c21a63 (2026-08-04).
Data as JSON: /data/errors/991e591036f57ad4.json.
Report an issue: GitHub.