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 as MongoInvalidArgumentError by FindOperators.replaceOne() (src/bulk/common.ts:749) when the replacement document contains MongoDB atomic operators ($set, $inc, etc.). replaceOne performs a full-document replacement, so the body must be a plain document with the new field values; mixing in $-operators would be rejected by the server and is caught client-side first.
Solutions
- Strip the $set wrapper and pass the literal fields: replaceOne({ a: 1, b: 2 }).
- If you actually want partial updates, switch the call to updateOne/update (which require the operators).
- Audit shared builders so they do not unconditionally prefix field keys with $ operators.
Example fix
// before
bulk.find({ _id: 1 }).replaceOne({ $set: { name: 'Sam' } });
// after
bulk.find({ _id: 1 }).replaceOne({ name: 'Sam' }); Defensive patterns
Strategy: validation
Validate before calling
function isPlainReplacement(doc: unknown): boolean {
if (doc == null || typeof doc !== 'object' || Array.isArray(doc)) return false;
return !Object.keys(doc as Record<string, unknown>).some(k => k.startsWith('$'));
}
if (!isPlainReplacement(replacement)) {
throw new Error('replaceOne body must be a plain document, no $-operators');
} Type guard
function isReplacementBody(doc: unknown): doc is Record<string, unknown> {
if (doc == null || typeof doc !== 'object' || Array.isArray(doc)) return false;
return !Object.keys(doc).some(k => k.startsWith('$'));
} Try / catch
try {
bulk.find(filter).replaceOne(replacement);
} catch (e) {
if (e instanceof MongoInvalidArgumentError && /must not use atomic operators/.test(e.message)) {
// strip $set wrapper if you accidentally wrapped a replacement
const inner = (replacement as any).$set ?? replacement;
bulk.find(filter).replaceOne(inner);
} else throw e;
} Prevention
- Treat replaceOne bodies as plain new documents, never as $-operator documents.
- Do not share a single field-builder between update and replace code paths.
- Add a unit test asserting replaceOne bodies contain no $-prefixed keys.
- When migrating updateOne -> replaceOne, remember to drop $set.
When it happens
Trigger: Calling bulkOp.find(filter).replaceOne({ $set: { a: 1 } }) instead of a plain replacement like replaceOne({ a: 1 }). Guarded by hasAtomicOperators(replacement) at src/bulk/common.ts:748.
Common situations: Developers switch from updateOne to replaceOne without removing the $set wrapper, or share a helper that always wraps fields in $set across both update and replace code paths. Also arises from copy-pasting an update document into a replace call.
Related errors
- Raw operations are not allowed
- Update document requires atomic operators
- Argument "operations" must be an array of documents
- Bulk find operation must specify a selector
- bulkWrite only supports insertOne, updateOne, updateMany…
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/c63ddc841faa3019.
Report an issue: GitHub.
Appendix: 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 dce7939f86)