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 as a MongoInvalidArgumentError by the ReplaceOneOperation constructor when the replacement document contains atomic operators (keys beginning with '$'). replaceOne substitutes the entire matched document with the supplied replacement, so field-update operators like $set are illegal; those belong to updateOne/updateMany. The check uses hasAtomicOperators() at update.ts:234.
Solutions
- If you meant partial updates with operators, use collection.updateOne(filter, { $set: { a: 1 } }).
- If you meant a full replacement, remove all '$'-prefixed keys and supply the complete document.
- Add a preflight check that no replacement key starts with '$' before calling replaceOne.
Example fix
// before
await collection.replaceOne({ _id: id }, { $set: { status: 'on' } });
// after
await collection.updateOne({ _id: id }, { $set: { status: 'on' } }); Defensive patterns
Strategy: validation
Validate before calling
function isPlainReplacement(v: unknown) {
return typeof v === 'object' && v !== null && !Array.isArray(v)
&& !Object.keys(v).some(k => k.startsWith('$'));
}
if (!isPlainReplacement(replacement)) {
throw new TypeError('replacement must not contain atomic operators; use updateOne for $ ops');
}
await collection.replaceOne(filter, replacement); Type guard
function isReplacementDoc(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v)
&& Object.keys(v).every(k => !k.startsWith('$'));
} Prevention
- Remember: replaceOne = full document, updateOne = $ operators.
- Run a key-prefix check on dynamic replacements before calling replaceOne.
- When refactoring between update and replace, switch the API and the document shape together.
When it happens
Trigger: Calling collection.replaceOne(filter, { $set: { a: 1 } }) instead of collection.updateOne. Any replacement whose first key starts with '$' triggers this.
Common situations: Copy-pasting an update document into a replaceOne call; confusion between replaceOne (full document) and updateOne (operators); refactoring and forgetting to switch APIs.
Related errors
- Replacement document must not contain atomic operators
- Argument "replacement" must be an object
- Update document requires atomic operators
- Update document requires atomic operators
- Argument "filter" must be an object
AI-assisted analysis of mongodb/node-mongodb-native@dce7939f86 (2026-08-11).
Data as JSON: /api/errors/449923bb9cf1f6f1.
Report an issue: GitHub.
Appendix: 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 dce7939f86)