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
Advertisement728 x 90
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
$matchafter an expensive stage like$group— this defeats much of the performance benefit; put$matchas early as possible in the pipeline (ideally first) so later stages process fewer documents. $projectsilently dropping fields you needed later — once a field is excluded by$project, it's gone for every subsequent stage in the pipeline, including$group/$sortfurther down. Keep any field you'll need later in the projection.- Assuming
$matchinside aggregation is identical tofind()'s query language — it's very close, but$matchcan't use certain geo-query operators the same way, and it can't reference computed fields from an earlier$project/$groupunless you explicitly carry them through.
Key Takeaways
$matchshould usually come early in the pipeline to cut down the data as soon as possible.$project's_idis included by default — set it to0explicitly to drop it.1includes a field,0excludes it, same rules asfind()projections.
Advertisement320 x 100