What it means
Names that start with $ are operators to MongoDB ($set, $inc, $cond). The server found one
where a field name should be, in a place it won’t store it, and refused the write with code 52,
DollarPrefixedFieldName.
The wording depends on the server version. Older servers said, as in this report on MongoDB’s forums:
The dollar ($) prefixed field '$cond' in 'energy.$cond' is not valid for storage.
MongoDB 8.0 says, for the cases that still fail:
The dollar ($) prefixed field '$price' in '$price' is not allowed in the context of an update's replacement document. Consider using an aggregation pipeline with $replaceWith.
Since 5.0, MongoDB can store field names that start with $, so some writes that used to fail now
succeed, and that can be worse (see the third cause).
Common causes
- An operator inside a replacement. A raw
updatecommand, or a driver call that sends a replacement, with$setinside it:{ qty: 3, $set: { a: 1 } }. Recent drivers catch the simple case first and sayReplacement document must not contain atomic operators. $setor$renamewith a top-level$name:$set: { "$price": 5 }, often from user data whose keys weren’t checked.- An aggregation expression in a classic update:
$set: { status: { $cond: … } }. Older servers refused it with “not valid for storage”. MongoDB 8.0 accepts it and stores the$condobject as data, so the document getsstatus: { '$cond': [ … ] }instead of a value. - Writing JSON from outside (a webhook payload, a MongoDB Extended JSON export) as a
replacement or through
$set, when some of its top-level keys start with$.
How to fix it
Use a pipeline when the new value is an expression
Wrap the update in an array. A pipeline update evaluates $cond, $concat, $ifNull and field
references such as "$qty":
db.stock.updateOne(
{ _id: 1 },
[{ $set: { status: { $cond: [{ $gt: ["$qty", 0] }, "in stock", "sold out"] } } }]
)
Keep operators and replacements apart
To change some fields, send only operators: { $set: { qty: 3, a: 1 } }. To replace the document,
send a plain document with no $ keys (replaceOne). Don’t mix the two.
Clean the keys of outside data
If the keys come from users or other systems, rename those that start with $ (and those that
contain .) before you store them, for example $price to price. That also keeps them
queryable: $-prefixed fields can’t be indexed or checked by $jsonSchema.
If the $ name is data you must keep
Use $setField in a pipeline to write it, and $getField to read it:
db.stock.updateOne({ _id: 1 }, [
{ $replaceWith: { $setField: { field: { $literal: "$price" }, input: "$$ROOT", value: 5 } } }
])
db.stock.find({ $expr: { $eq: [{ $getField: { $literal: "$price" } }, 5] } })
Find documents that stored an expression by mistake
If a classic update stored $cond as data, find those documents and set the field again with a
pipeline:
db.stock.find({ "status.$cond": { $exists: true } })
Reproduce it
MongoDB 8.0.32, mongosh 2.12.0, in a scratch database:
db.items.updateOne({ _id: 1 }, { $set: { "$price": 5 } })
MongoServerError: The dollar ($) prefixed field '$price' in '$price' is not allowed in the context of an update's replacement document. Consider using an aggregation pipeline with $replaceWith.
The error’s code was 52. A raw update command with { qty: 3, $set: { a: 1 } } returned the same
code in writeErrors, naming '$set'. $rename: { qty: "$qty" } named '$qty'. Through
mongosh’s replaceOne and findOneAndReplace, a replacement with $set in it never reached the
server:
MongoInvalidArgumentError: Replacement document must not contain atomic operators
These were accepted: insertOne({ "$price": 5 }), a nested { meta: { "$price": 5 } }, and
$set: { "meta.$price": 5 }. A classic update with an expression:
db.stock.updateOne({ _id: 1 }, { $set: { status: { $cond: [{ $gt: ["$qty", 0] }, "in stock", "sold out"] } } })
db.stock.findOne()
{
_id: 1,
qty: 3,
status: { '$cond': [ { '$gt': [ '$qty', 0 ] }, 'in stock', 'sold out' ] }
}
The same $set as a pipeline stored status: 'in stock'. The $setField update added '$price': 5,
and the $getField query found it. createIndex({ "$price": 1 }) was refused: Index key contains an illegal field name: field name starts with '$'.
In Inlet
Inlet’s document editor updates only the fields you edited and keeps each value’s type. Queries use
mongosh syntax, so a pipeline update (db.stock.updateOne({ _id: 1 }, [{ $set: … }])) runs as it
does in mongosh, and you can check the result in the inspector.