Upsert: Update or Insert
An upsert combines update behavior with conditional insertion. When an update operation has upsert: true, MongoDB inserts a document if no document matches the filter.
Example: Upsert a settings document
db.userSettings.updateOne(
{ userId: "user-1001" },
{
$set: {
theme: "dark",
language: "en"
}
},
{ upsert: true }
);
If a matching document exists, its selected fields are updated. Otherwise, MongoDB creates a document based on the filter and update operation.
$setOnInsert
$setOnInsert applies fields only when the upsert inserts a new document.
db.userSettings.updateOne(
{ userId: "user-1001" },
{
$set: { theme: "dark" },
$setOnInsert: {
createdAt: new Date(),
language: "en"
}
},
{ upsert: true }
);
If the document already exists, theme is updated but the $setOnInsert fields are not applied. If no document matches, those fields are included in the inserted document.
Upsert and filter fields
When an upsert inserts a document, MongoDB derives parts of the inserted document from the equality conditions in the filter, together with the update modifications and operation-specific behavior.
Do not assume that arbitrary range conditions or complex logical expressions will be converted into a useful document structure. Design upsert filters around stable identifying keys.
The concurrency problem
Suppose two requests attempt to upsert the same logical record at approximately the same time. Without an appropriate unique index, the application may create multiple documents representing the same logical entity if the filters and execution allow it.
A unique index on the logical key is often essential:
db.userSettings.createIndex(
{ userId: 1 },
{ unique: true }
);
The index enforces uniqueness of userId values across documents, subject to the index's handling of missing or null values and any partial-index configuration.
Applications should still handle duplicate-key errors and define how concurrent requests are retried or reconciled.
Idempotent application design
An upsert keyed by a stable unique identifier can help implement idempotent write behavior. However, not every update is idempotent. For example, $inc changes the value each time the operation executes.
If an application retries an increment after an ambiguous network failure, the increment may be applied more than once unless the workflow provides additional deduplication or transactional safeguards.
An upsert filter identifies the document to update or create. When the application requires one document per logical key, enforce that rule with an appropriate unique index and handle duplicate-key errors.
db.upsertDemo.createIndex({ userId: 1 }, { unique: true });
db.upsertDemo.updateOne(
{ userId: "user-1001" },
{
$set: {
theme: "dark"
},
$setOnInsert: {
language: "en",
createdAt: new Date()
}
},
{ upsert: true }
);
db.upsertDemo.findOne({ userId: "user-1001" });- An upsert updates a match or inserts a document when no match exists.
- $setOnInsert applies fields only when the upsert inserts a document.
- Stable identifying filters are important for predictable upsert behavior.
- A unique index helps enforce one document per logical key.
- An increment is not idempotent simply because it is performed through an upsert.
When does `$setOnInsert` apply its fields?