$addFields adds computed fields and passes everything else through untouched. $set is the same stage under a shorter name, added in MongoDB 4.2 1,815 so pipelines read like update operators. $unset is the inverse — { $unset: 'items' } for one field, an array for several. All three stream.
db.orders.aggregate([
{ $match: { _id: 'o-1001' } },
{ $set: { total: { $sum: { $map: { input: '$items', as: 'i',
in: { $multiply: ['$$i.qty', '$$i.price'] } } } } } },
{ $addFields: { grand: { $add: ['$total', '$shipping'] } } },
{ $unset: ['items', 'customerId', 'placedAt', 'status', 'shipping'] }
])[ { _id: 'o-1001', channel: 'web', total: 150, grand: 162 } ]Because the second stage reads $total, which the first created, two small stages beat one large $project: derived values build on one another instead of being written out in full every time. Assigning to an existing name overwrites it, which makes { $set: { status: 'archived' } } a clean way to normalize a field.
Sometimes the value you want is the whole document. $replaceWith promotes any expression to the root, and $replaceRoot: { newRoot: <expr> } is its older spelling. It pairs naturally with $lookup: after joining an order to its customer, { $replaceWith: { $mergeObjects: [{ $first: '$c' }, { order: '$_id' }] } } returns the customer document with an order field attached.