Menu

MongoDB $setOnInsert: Set Defaults During Upsert

Updated on

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.

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:

  • email and updatedAt are updated by $set.
  • createdAt and status are not changed by $setOnInsert.

If no matching document exists:

  • MongoDB inserts a new document.
  • It applies both $set and $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.

Further reading