$addFields, $replaceRoot, and $mergeObjects
Learners will add computed fields, promote nested sub-documents to the root level, and merge objects within the pipeline.
$addFields, $replaceRoot, and $mergeObjects is a free MongoDB Academy lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the MongoDB Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Three Reshaping Stages
While $project is the primary document-reshaping tool, three additional stages offer more targeted transformations: $addFields adds new fields while preserving all existing ones, $replaceRoot promotes a sub-document to become the new root document, and $mergeObjects merges multiple objects into one. Together they cover reshaping patterns that would be verbose or impossible with $project alone.
$addFields: Adding Without Dropping
$addFields (also available as its alias $set) passes through all existing document fields and adds or overwrites only the specified fields. This is the key difference from $project in inclusion mode, where you must explicitly list every field you want to keep. Use $addFields whenever you want to enrich a document with computed fields without listing every existing field.
// $addFields preserves all existing fields
db.products.aggregate([{
$addFields: {
// Add computed fields; all original fields (name, price, etc.) are kept
totalWithTax: { $multiply: ['$price', 1.1] },
priceLabel: { $concat: ['$', { $toString: '$price' }] },
isExpensive: { $gt: ['$price', 1000] }
}
}]);
// All original product fields PLUS the three new computed fields$set: The Modern Alias for $addFields
MongoDB 4.2 introduced $set as an alias for $addFields in both aggregation pipelines and update operations. The two are completely interchangeable in pipeline context. $set is the preferred modern spelling because it is more intuitive and mirrors the $set update operator used in updateOne(). Use whichever you find more readable.
// $set (modern) and $addFields (classic) are identical
db.orders.aggregate([{
$set: { // same as $addFields
totalWithShipping: { $add: ['$amount', '$shippingFee'] },
daysToDeliver: {
$divide: [
{ $subtract: ['$deliveredAt', '$createdAt'] },
1000 * 60 * 60 * 24
]
}
}
}]);Overwriting Existing Fields With $addFields
When you specify a field name that already exists in the document, $addFields overwrites the existing value with the new computed value. This is useful for normalising data—for example, converting a string field to lowercase in the pipeline output without modifying the stored document.
// Normalise email to lowercase in pipeline output
db.users.aggregate([{
$addFields: {
email: { $toLower: '$email' } // overwrites existing email field
}
}]);
// The stored document is unchanged; only the pipeline output is normalised
// Promote a nested field to the top level by overwriting
db.orders.aggregate([{
$addFields: {
city: '$shippingAddress.city' // copy nested field to top level
}
}]);$replaceRoot: Making a Sub-Document the Root
$replaceRoot replaces the entire root document with a specified sub-document or expression. The new root must be an object. This is useful after a $lookup + $unwind when you want the joined document to be the primary shape, or when you want to surface a nested sub-document as the output document while discarding the parent fields.
// Document: { order: { id: 1, amount: 100, customer: { name: 'Alice' } } }
// Make the nested 'order' sub-document the new root
db.transactions.aggregate([{
$replaceRoot: { newRoot: '$order' }
}]);
// Output: { id: 1, amount: 100, customer: { name: 'Alice' } }
// The outer 'order' wrapper is gone; order's fields are now at the root level$replaceWith: Alias for $replaceRoot
MongoDB 4.2 introduced $replaceWith as a more concise alias for $replaceRoot. Instead of { $replaceRoot: { newRoot: expr } }, you can write { $replaceWith: expr }. The two are completely equivalent. $replaceWith is often used after $lookup/$unwind to surface the joined document as the primary result.
// After lookup + unwind, make the joined product the root
db.orders.aggregate([
{ $lookup: { from: 'products', localField: 'productId', foreignField: '_id', as: 'product' } },
{ $unwind: '$product' },
// Promote product to root, but keep orderId
{ $replaceWith: {
$mergeObjects: ['$product', { orderId: '$_id', orderAmount: '$amount' }]
}}
]);$mergeObjects: Combining Two Objects
$mergeObjects merges multiple objects into a single object. When two objects have the same field, the last one wins. It is commonly used inside $replaceWith or $addFields to flatten a joined document and add extra fields at the same time. $mergeObjects can accept an array of objects or be used as a $group accumulator.
// Merge two objects - last key wins on conflict
db.users.aggregate([{
$replaceWith: {
$mergeObjects: [
{ defaultRole: 'viewer', isActive: false }, // defaults first
'$$ROOT', // actual doc overrides defaults
{ processedAt: '$$NOW' } // add computed field last
]
}
}]);$mergeObjects as a $group Accumulator
When used as a $group accumulator, $mergeObjects merges all documents in a group into a single object. This is useful for combining multiple partial documents that together form a complete record—for example, gathering all update events for an entity and merging them into a single 'current state' document.
// Merge all update events per entity into one current state
db.events.aggregate([
{ $sort: { timestamp: 1 } }, // process oldest first
{ $group: {
_id: '$entityId',
// Merge all event deltas into one object; later events override earlier
currentState: { $mergeObjects: '$delta' }
}}
]);
// Each group's 'delta' objects are merged sequentially, last winsPractical Pattern: Flatten $lookup Result
A very common pattern is to use $replaceWith + $mergeObjects to flatten a looked-up document into the parent document's root, adding the parent's relevant fields alongside the joined document's fields. This produces a clean, flat output object that's easy to serialise and send to the client.
// Flatten order + product into a single output document
db.orders.aggregate([
{ $lookup: {
from: 'products',
localField: 'productId',
foreignField: '_id',
as: 'product'
}},
{ $unwind: '$product' },
{ $replaceWith: {
$mergeObjects: [
'$product', // all product fields at root
{ orderId: '$_id', qty: '$qty', total: { $multiply: ['$product.price', '$qty'] } }
]
}}
]);
// Clean flat output: { name, category, price, orderId, qty, total }$unset: Removing Fields
$unset (available since MongoDB 4.2) removes specific fields from documents in the pipeline. It's the inverse of $addFields: while $addFields adds fields without touching others, $unset removes specified fields while passing through everything else. This is cleaner than using $project with all fields set to 1 just to exclude one field.
// Remove sensitive fields from output
db.users.aggregate([{
$unset: ['password', 'apiKey', '__v'] // array of field names to remove
}]);
// All fields except password, apiKey, and __v are passed through
// Remove a single field
db.orders.aggregate([{ $unset: 'internalNotes' }]);
// Remove nested field with dot notation
db.users.aggregate([{ $unset: 'profile.ssn' }]);Combining $addFields, $replaceRoot, and $mergeObjects
These three stages work together in sophisticated reshaping pipelines. A typical sequence is: use $addFields to compute derived fields, use $lookup to join related data, use $unwind to flatten the join, then use $replaceWith + $mergeObjects to produce a clean output shape. This combination covers most API response shaping needs without application-side transformation.
db.invoices.aggregate([
{ $addFields: { taxAmount: { $multiply: ['$subtotal', 0.08] } } },
{ $lookup: { from: 'clients', localField: 'clientId', foreignField: '_id', as: 'client' } },
{ $unwind: '$client' },
{ $replaceWith: {
$mergeObjects: [
{ invoiceId: '$_id', subtotal: '$subtotal', taxAmount: '$taxAmount' },
{ clientName: '$client.name', clientEmail: '$client.email' }
]
}},
{ $unset: ['_id'] }
]);Quick Check
Test your understanding of $addFields, $replaceRoot, and $mergeObjects.
Lesson Recap
In this lesson you learned: $addFields/$set adds fields while preserving all existing ones, $replaceRoot/$replaceWith promotes a sub-document to the root, and $mergeObjects combines multiple objects with last-key-wins semantics. Next up we explore $out and $merge for writing pipeline results to collections.
Frequently asked questions
Is the “$addFields, $replaceRoot, and $mergeObjects” lesson free?
Yes — the full text of “$addFields, $replaceRoot, and $mergeObjects” is free to read here on the web, and the MongoDB Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the MongoDB Academy course, upgrade to CoddyKit PRO.
What will I learn in “$addFields, $replaceRoot, and $mergeObjects”?
Learners will add computed fields, promote nested sub-documents to the root level, and merge objects within the pipeline. You practise MongoDB Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start MongoDB Academy?
No prior experience is required. MongoDB Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “$addFields, $replaceRoot, and $mergeObjects” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this MongoDB Academy lesson?
Yes. Every MongoDB Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- $lookup: Joining Collections in the Pipeline
- $unwind: Deconstructing Array Fields
- $addFields, $replaceRoot, and $mergeObjects
- $out and $merge: Writing Pipeline Results