MongoDB $mergeObjects Accumulator

Beginner
⏱️ 10 min read
📚 Updated: Jul 2026
🎯 5 Examples
Aggregation

What You’ll Learn

The $mergeObjects accumulator folds many sub-documents in each $group bucket into one combined object—perfect for rolling up quarterly metrics, profile patches, or nested maps without manual field copying.

01

One object per group

Merge nested fields.

02

Single operand

Accumulator syntax.

03

Last wins

Duplicate keys overwrite.

04

Null ignored

Skips null documents.

05

vs expression form

Array merge elsewhere.

06

vs $setUnion

Objects vs arrays.

Definition and Usage

$mergeObjects is a MongoDB operator that combines document fields into a single embedded document. Used as an accumulator inside $group, it evaluates one document expression on every row in the group and merges all resulting objects together.

💡
Beginner tip

Think of it like spreading objects together in JavaScript: { ...obj1, ...obj2, ...obj3 }. Each grouped document contributes its object; fields with the same name keep the last value seen.

Common uses include merging quarterly sales maps per product, combining user preference patches, or flattening nested metadata after grouping. When you need to merge two specific documents in a non-group stage, use the expression form with an array: { $mergeObjects: [ docA, docB ] }.

📝 Syntax

As a $group accumulator, $mergeObjects takes one document expression:

mongosh
{
  $group: {
    _id: <expression>,
    <field>: {
      $mergeObjects: <document expression>
    }
  }
}

As a general aggregation expression (not an accumulator), it accepts an array of documents:

mongosh
{
  $mergeObjects: [
    <document1>,
    <document2>,
    ...
  ]
}

Syntax Rules

  • Accumulator operand — exactly one expression per document, e.g. "$quantity" or "$profile".
  • Expression operand — array of two or more documents to merge in order (used in $project, $addFields, $replaceRoot, etc.).
  • Last wins — duplicate field names keep the value from the last merged document.
  • Null skipped — null operands are ignored; all-null groups yield { }.
  • Stages — accumulator form works in $group, $bucket, and $bucketAuto.
  • Non-objects — operands must resolve to documents; scalars and arrays are not valid merge inputs.
mongosh
db.sales.aggregate([
  {
    $group: {
      _id: "$item",
      mergedSales: {
        $mergeObjects: "$quantity"
      }
    }
  }
]);

⚡ Quick Reference

QuestionAnswer
Stage$group accumulator (also $bucket)
Accumulator syntax{ $mergeObjects: "$field" }
Expression syntax{ $mergeObjects: [ doc1, doc2 ] }
ReturnsOne merged object per group
Duplicate keysLast document wins
Null handlingIgnored; all null → { }
Merge map field
$mergeObjects:
  "$quantity"

Quarterly keys roll up

Merge profile
$mergeObjects:
  "$profile"

Patch objects per user

Expression merge
$mergeObjects: [
  "$defaults",
  "$overrides"
]

Two docs in $project

After $lookup
$mergeObjects: [
  { $arrayElemAt:
    ["$items", 0] },
  "$$ROOT"
]

Join + flatten pattern

🧰 Parameters

The accumulator and expression forms use different operand shapes:

document (accumulator) Required

Single expression evaluated on each document in the group. Must resolve to an object (or null, which is skipped).

$mergeObjects: "$quantity"
$mergeObjects: "$profile"
[ documents ] (expression) Expression only

Array of document expressions merged left to right. Used outside accumulator context—e.g. in $replaceRoot after a join.

$mergeObjects: [
  "$a", "$b"
]
overwrite behavior Behavior

When two sources define the same key, the later source wins. Order within a group follows MongoDB’s internal processing—not guaranteed unless you $sort first.

// last doc sets status
null handling Edge case

Null operands are dropped. Missing fields evaluate to null and are skipped. An all-null group returns { }.

// null → ignored

Examples Gallery

Sales records with nested quantity maps—merge quarterly figures per product, explore overwrite rules, and combine collections with the expression form.

📚 Getting Started

Sample sales data and the core $mergeObjects grouping pattern from the MongoDB docs.

Example 1 — Sample sales collection

mongosh
db.sales.insertMany([
  {
    _id: 1,
    year: 2017,
    item: "A",
    quantity: { "2017Q1": 500, "2017Q2": 500 }
  },
  {
    _id: 2,
    year: 2016,
    item: "A",
    quantity: {
      "2016Q1": 400,
      "2016Q2": 300,
      "2016Q3": 0,
      "2016Q4": 0
    }
  },
  {
    _id: 3,
    year: 2017,
    item: "B",
    quantity: { "2017Q1": 300 }
  },
  {
    _id: 4,
    year: 2016,
    item: "B",
    quantity: { "2016Q3": 100, "2016Q4": 250 }
  }
]);

How It Works

Each row stores quarterly counts inside a nested quantity object. Grouping by item lets you fold all quarters for product A or B into one map.

Example 2 — Merge quantity maps per item

mongosh
db.sales.aggregate([
  {
    $group: {
      _id: "$item",
      mergedSales: {
        $mergeObjects: "$quantity"
      }
    }
  },
  { $sort: { _id: 1 } }
]);

/* Result:
[
  {
    _id: "A",
    mergedSales: {
      "2017Q1": 500, "2017Q2": 500,
      "2016Q1": 400, "2016Q2": 300,
      "2016Q3": 0, "2016Q4": 0
    }
  },
  {
    _id: "B",
    mergedSales: {
      "2017Q1": 300,
      "2016Q3": 100, "2016Q4": 250
    }
  }
]
*/

How It Works

Every document for item A contributes its quantity keys. Because quarter names differ across years, keys accumulate without collision—one object holds the full history.

📈 Practical Patterns

Profile patches, overwrite behavior, and joining collections with the expression form.

Example 3 — Merge profile patches per user

Users receive partial profile updates stored as sub-documents. Merge them into one profile per user:

mongosh
db.profileUpdates.insertMany([
  { userId: "u1", patch: { theme: "dark", lang: "en" } },
  { userId: "u1", patch: { notifications: true } },
  { userId: "u2", patch: { theme: "light" } },
  { userId: "u2", patch: { lang: "fr", timezone: "CET" } }
]);

db.profileUpdates.aggregate([
  {
    $group: {
      _id: "$userId",
      profile: {
        $mergeObjects: "$patch"
      }
    }
  },
  { $sort: { _id: 1 } }
]);

/* u1 → { theme: "dark", lang: "en", notifications: true }
   u2 → { theme: "light", lang: "fr", timezone: "CET" } */
*/

How It Works

Each patch adds new keys to the merged object. This pattern replaces manual $push + post-processing when you only need the final combined settings object.

Example 4 — Duplicate keys: last document wins

When two documents in the same group share a field name, the later value overwrites the earlier one:

mongosh
db.settings.insertMany([
  { app: "mobile", config: { theme: "light", debug: false } },
  { app: "mobile", config: { theme: "dark", analytics: true } }
]);

db.settings.aggregate([
  { $sort: { _id: 1 } },
  {
    $group: {
      _id: "$app",
      config: {
        $mergeObjects: "$config"
      }
    }
  }
]);

/* Result:
[
  {
    _id: "mobile",
    config: {
      theme: "dark",      // overwritten by doc 2
      debug: false,       // kept from doc 1
      analytics: true     // added by doc 2
    }
  }
]
*/

How It Works

$sort before $group when overwrite order matters. Without sorting, MongoDB may process documents in an undefined order within the group.

Example 5 — Expression form after $lookup

Join orders with a product catalog, then merge matched item details into each order document:

mongosh
db.orders.insertMany([
  { _id: 1, item: "abc", price: 12, ordered: 2 },
  { _id: 2, item: "jkl", price: 20, ordered: 1 }
]);

db.items.insertMany([
  { _id: 1, item: "abc", description: "product 1", instock: 120 },
  { _id: 2, item: "def", description: "product 2", instock: 80 },
  { _id: 3, item: "jkl", description: "product 3", instock: 60 }
]);

db.orders.aggregate([
  {
    $lookup: {
      from: "items",
      localField: "item",
      foreignField: "item",
      as: "fromItems"
    }
  },
  {
    $replaceRoot: {
      newRoot: {
        $mergeObjects: [
          { $arrayElemAt: ["$fromItems", 0] },
          "$$ROOT"
        ]
      }
    }
  },
  { $project: { fromItems: 0 } }
]);

/* Merged order + item fields in one document */
*/

How It Works

This uses the expression form (array syntax), not the accumulator. Item fields merge first; $$ROOT order fields overwrite on name clashes—here _id and item come from the order.

🧠 How $mergeObjects Works

1

Documents bucketed

$group assigns each document to a bucket via _id.

Group
2

Object extracted

The operand expression (e.g. "$quantity") is evaluated per document; null results are skipped.

Evaluate
3

Fields combined

MongoDB merges all objects in the group. New keys are added; duplicate keys keep the last value.

Merge
=

One object returned

A single embedded document per group containing all merged fields—or { } if nothing valid was found.

📝 Notes

  • Accumulator form accepts one document expression; expression form accepts an array of documents.
  • Duplicate field names keep the value from the last merged document—use $sort when order matters.
  • Null operands are ignored; an all-null group returns an empty object { }.
  • Operands must resolve to objects, not scalars or bare arrays.
  • Also available as accumulator in $bucket and $bucketAuto stages.
  • Previous topic: $maxN. Next: $min.

Conclusion

$mergeObjects is the go-to accumulator when grouped documents each carry a partial object and you need one combined result—quarterly metrics, config patches, or metadata maps.

Remember the two syntax shapes: single operand in $group, array operand elsewhere. When keys collide, the last document wins—sort first if that order is business-critical. Next up: $min for finding minimum values per group.

💡 Best Practices

✅ Do

  • Use unique key names inside nested maps (e.g. "2017Q1") to avoid accidental overwrites
  • Add $sort before $group when last-wins order must be deterministic
  • Filter null patches in $match to keep merged objects clean
  • Use expression form with $lookup + $replaceRoot to flatten joined documents
  • Validate merged output shape with $project before writing to downstream collections

❌ Don’t

  • Pass an array operand inside $group—accumulator form needs a single expression
  • Expect deep recursive merge—only top-level fields are merged (nested objects replace wholesale)
  • Confuse $mergeObjects with $setUnion (objects vs array deduplication)
  • Merge scalar fields directly—wrap them in an object or use a different accumulator
  • Assume document processing order within a group without an explicit $sort

Key Takeaways

Knowledge Unlocked

Five things to remember about $mergeObjects

Use these points when folding nested objects inside $group.

5
Core concepts
📄 02

Single operand

In $group context.

Syntax
🔄 03

Last wins

Duplicate keys.

Behavior
🚫 04

Null skipped

Empty → { }.

Edge case
🔗 05

Array form

For $lookup joins.

Pattern

❓ Frequently Asked Questions

$mergeObjects combines multiple sub-documents from documents in the same $group bucket into one embedded document. Each document contributes its object fields; the result is a single merged object per group.
Inside $group (and also $bucket / $bucketAuto): { merged: { $mergeObjects: "$profile" } }. The operand must be a single expression that resolves to a document—not an array of documents.
As an accumulator: { $mergeObjects: <one document expression> }. As a general expression: { $mergeObjects: [ doc1, doc2, ... ] }. The array form merges a fixed list of documents in order; the accumulator form merges one object per grouped document.
Later values overwrite earlier ones. If three documents in a group all have a status field, the merged object keeps the value from the last document MongoDB processes for that field.
No. Null operands are ignored. If every document in a group resolves to null, $mergeObjects returns an empty object { }.
$mergeObjects merges object fields into one document. $setUnion works on arrays and returns unique array elements. Use $mergeObjects for nested maps like quarterly sales quantities; use $setUnion for tag lists.
Did you know?

$mergeObjects performs a shallow merge—nested objects are replaced as a whole, not merged field-by-field at every depth. For deep merging you need custom logic or multiple pipeline stages. See the official $mergeObjects docs.

Continue the Accumulators Series

Combine nested objects with $mergeObjects, then learn $min for finding the lowest value per group.

Next: $min →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful