MongoDB $setOnInsert: Set Defaults During Upsert
MongoDB $setOnInsert assigns fields only when an upsert creates a new document. If the update matches an existing document, $setOnInsert does nothing.
That makes it useful for immutable creation defaults such as createdAt, an initial status, or a default quota.
Syntax
db.collection.updateOne(
<filter>,
{
$setOnInsert: {
<field1>: <value1>,
<field2>: <value2>
}
},
{ upsert: true }
)
The key requirement is upsert: true. Without an insert caused by the upsert, there is nothing for $setOnInsert to apply to.
Recommended pattern: combine $set and $setOnInsert
Most applications need some fields updated every time and other fields set only on creation:
db.users.updateOne(
{ username: "JohnDoe" },
{
$set: {
email: "[email protected]",
updatedAt: new Date()
},
$setOnInsert: {
createdAt: new Date(),
status: "active"
}
},
{ upsert: true }
)
If JohnDoe already exists:
emailandupdatedAtare updated by$set.createdAtandstatusare not changed by$setOnInsert.
If no matching document exists:
- MongoDB inserts a new document.
- It applies both
$setand$setOnInsert.
Example: initialize counters only once
db.products.updateOne(
{ sku: "A-100" },
{
$set: {
name: "Keyboard",
updatedAt: new Date()
},
$setOnInsert: {
createdAt: new Date(),
viewCount: 0
}
},
{ upsert: true }
)
Later updates can change the product without resetting viewCount or createdAt.
Supported update methods
You can use upsert with methods including:
updateOne()updateMany()findOneAndUpdate()
Choose the method that matches how many documents your filter is allowed to affect.
Common mistakes
Expecting $setOnInsert to update an existing document
It does not. Put fields that must change on every matching update under $set.
Forgetting upsert: true
$setOnInsert is designed for the insert side of an upsert.
Putting mutable timestamps under $setOnInsert
A field such as updatedAt normally belongs under $set; a field such as createdAt normally belongs under $setOnInsert.