$match and $project

Filtering and Reshaping with match and project

$match takes exactly the query document you would hand to find(); every operator from Querying Documents works unchanged. What it adds is $expr, which lets a filter call aggregation expressions and so compare two fields of the same document — something the plain query language cannot do. $project then decides which fields leave the stage: 1 includes a field, 0 removes it, any expression creates a computed one, and _id is included unless you set _id: 0.

Filtering with $expr, then reshaping into a report document
db.orders.aggregate([
  { $match: { status: { $in: ['delivered', 'shipped'] },
              placedAt: { $gte: ISODate('2026-04-01') },
              $expr: { $gt: [{ $size: '$items' }, 1] } } },
  { $project: { _id: 0, order: '$_id',
      month: { $dateToString: { format: '%Y-%m', date: '$placedAt' } },
      lines: { $size: '$items' },
      total: { $add: ['$shipping',
        { $sum: { $map: { input: '$items', as: 'i',
                          in: { $multiply: ['$$i.qty', '$$i.price'] } } } } ] } } },
  { $limit: 3 }
])
Output
[
  { order: 'o-1012', month: '2026-05', lines: 2, total: 462 },
  { order: 'o-1011', month: '2026-05', lines: 3, total: 180 },
  { order: 'o-1009', month: '2026-04', lines: 2, total: 655 }
]

Keep indexable conditions out of $expr: it can use an index for a plain comparison on a field path, but a wrapper such as $size makes it unindexable. Pair it with ordinary operators, as above, where status and placedAt drive the index scan.

A leading dollar sign turns a string into a field path: '$_id' means "the value of _id", '_id' is the literal, and '$$i.price' reads the variable $map bound with as. You may list fields to remove instead — { $project: { items: 0, channel: 0 } } — but not mix the modes: status: 1 beside channel: 0 fails with Cannot do exclusion on field channel in inclusion projection. The one legal mixture is _id: 0 in an inclusion projection. Reach for $project only when you want a new document shape; to add one field to documents you already have, use $addFields (Adding and Removing Fields), which will not silently drop a field somebody adds next month.