🍃 MongoDB Tutorial

MongoDB Aggregation: $match and $project Stages Explained

Learn how the $match aggregation stage filters documents like find(), and how $project selects and reshapes the fields returned from an aggregation pipeline.

$match and $project Stages

$match

$match filters documents entering the pipeline — it works exactly like the query object you'd pass to find(), and it's almost always the first stage, so later stages have less data to process.

db.employees.aggregate([
  { $match: { city: "Pune" } }
])

db.employees.aggregate([
  { $match: { city: "Pune", age: 26 } }
])
// city is Pune AND age is 26

db.employees.aggregate([
  { $match: { salary: { $gte: 50000 } } }
])

db.employees.aggregate([
  { $match: { lang: { $in: ['Hindi', 'Marathi'] } } }
])
// works on array fields too

db.employees.aggregate([
  { $match: { "reportinmgr.mgrid": 101 } }
])
// and embedded document fields

$project

$project selects which fields make it into the result — good practice, since returning every field when you only need two or three wastes bandwidth and processing.

db.employees.aggregate([
  { $project: { name: 1, email: 1 } }
])
// _id is included by default even though it wasn't listed

db.employees.aggregate([
  { $project: { _id: 0, name: 1, email: 1 } }
])
// exclude _id explicitly

db.employees.aggregate([
  { $project: { _id: 0, name: 1, email: 1, "reportinmgr.mgrid": 1 } }
])
// project a single field from an embedded document

Combining $match and $project

db.employees.aggregate([
  { $match: { salary: { $gte: 50000 } } },
  { $project: { _id: 0, name: 1, email: 1, salary: 1 } }
])

Filter first with $match, then shape the output with $project — this order keeps the pipeline efficient.

Common Errors & Troubleshooting

  • Putting $match after an expensive stage like $group — this defeats much of the performance benefit; put $match as early as possible in the pipeline (ideally first) so later stages process fewer documents.
  • $project silently dropping fields you needed later — once a field is excluded by $project, it's gone for every subsequent stage in the pipeline, including $group/$sort further down. Keep any field you'll need later in the projection.
  • Assuming $match inside aggregation is identical to find()'s query language — it's very close, but $match can't use certain geo-query operators the same way, and it can't reference computed fields from an earlier $project/$group unless you explicitly carry them through.

Key Takeaways

  • $match should usually come early in the pipeline to cut down the data as soon as possible.
  • $project's _id is included by default — set it to 0 explicitly to drop it.
  • 1 includes a field, 0 excludes it, same rules as find() projections.