The $match stage filters documents in a pipeline—like find() but inside aggregation. It is usually the first stage you reach for when you need “only rows where…” before sorting, joining, or grouping.
01
Query syntax
Same as find().
02
Comparisons
$gt, $gte, $lt.
03
$in / $regex
Lists & patterns.
04
$and / $or
Compound logic.
05
$expr
Field vs field.
06
Early filter
Index-friendly.
Fundamentals
Definition and Usage
$match is an aggregation stage that passes only documents matching your query to the next stage. Documents that fail the condition are dropped—reducing the pipeline’s working set.
💡
Beginner tip
These two are equivalent for a simple filter: db.products.find({ category: "Electronics", inStock: true }) db.products.aggregate([{ $match: { category: "Electronics", inStock: true } }]) Use aggregation when you need additional stages after the filter.
Place $match early—before $lookup, $group, or $sort—so MongoDB can use indexes and process fewer documents in expensive downstream stages.
Foundation
📝 Syntax
$match accepts a standard MongoDB query document:
mongosh
{ $match: { <query predicate> } }
Syntax Rules
Implicit AND — multiple fields in one object are ANDed: { status: "active", price: { $gte: 10 } }.
Comparison ops — $eq, $ne, $gt, $gte, $lt, $lte on field values.
Membership — $in and $nin for array membership tests.
Logic — $and, $or, $not for compound conditions.
$expr — use aggregation expressions to compare fields: { $expr: { $gt: ["$price", "$cost"] } }.
Index use — first $match on indexed fields can use IXSCAN like find().
All three conditions must pass
Index on category+inStock helps
Office Chair excluded (Furniture)
How It Works
Multiple keys in one $match object are ANDed together. Putting this stage first lets MongoDB use the compound index on category and inStock before sorting.
Example 3 — $in, $regex, and $or
mongosh
db.products.aggregate([
{
$match: {
$or: [
{ category: { $in: ["Electronics", "Furniture"] }, rating: { $gte: 4.5 } },
{ name: { $regex: /Pro$/i } }
],
inStock: true
}
},
{
$project: {
name: 1,
category: 1,
rating: 1,
_id: 0
}
}
])
// $or → high-rated in two categories OR name ending in "Pro"
// inStock: true applies to ALL branches (top-level AND)
// Widget Pro, Monitor Stand match via different $or paths
📤 Compound logic:
$or inside $match for alternatives
Top-level fields AND with $or block
$regex case-insensitive via /i flag
How It Works
$or matches if any branch passes; sibling fields at the same level still AND with the $or block. Use $and explicitly when you need clearer grouping of complex conditions.
📈 Practical Patterns
Post-join filtering and field comparisons.
Example 4 — $match after $lookup (filter joined data)
Pre-lookup: shrink order set
Post-lookup: filter on customer.tier
Dot notation for embedded/joined fields
How It Works
Always prefer a pre-$lookup$match when the filter applies to the input collection—it avoids joining rows you will discard anyway. Post-join $match is for conditions on foreign fields.
Example 5 — $expr: price greater than cost (profitable items)
Field-to-field comparison
$price and $cost on same doc
Regular $match cannot compare two fields
How It Works
Standard query syntax compares a field to a literal. $expr lifts aggregation expressions into the filter—essential for margin checks, date diffs, or array length conditions.
Compare
📋 $match vs related filtering tools
Tool / stage
Effect
Best for
$match
Filter pipeline documents by query
First-stage filter, pre/post join
find()
Filter at query API level
Simple reads without aggregation
$count
Return count of all input docs
Totals after $match (no row output)
$redact
Include/prune/restrict by condition
Security trimming nested docs
$facet sub-$match
Filter inside parallel branches
Different filters per facet view
🧠 How $match Works
1
Documents arrive
Each document from the previous stage (or the collection if first) is evaluated against the query.
Input
2
Predicate tested
MongoDB applies query operators—equality, ranges, $expr, etc. Index scan when the first $match supports it.
Filter
3
Non-matches dropped
Only passing documents continue—reducing load on $sort, $lookup, and $group.
Pass
=
🔍
Filtered stream
Downstream stages process a smaller, relevant subset of documents.
Important
📝 Notes
$match query syntax is identical to find() filter documents.
Place $matchas early as possible—especially before $lookup and $group.
The first$match on indexed fields can use indexes; later $match stages depend on prior stage output.
Use $expr when comparing two fields on the same document.
$geoNear cannot be preceded by $match—use its built-in query option instead.
$match is the pipeline’s workhorse filter: same syntax as find(), best placed early, and reusable after joins when you need conditions on related fields.
Combine with $sort, $group, and $lookup for full analytics pipelines. Next: $merge to write aggregation results into a collection.
Put $match first when filtering the source collection
Create indexes on fields used in early $match stages
Filter orders/rows before $lookup to reduce join cost
Use $expr for field-to-field comparisons
Run explain() on the aggregation to verify index use
❌ Don’t
Run $lookup or $group on the full collection when $match could filter first
Assume post-$group$match uses the same indexes as stage-one filters
Use $where or JavaScript predicates—prefer query operators
Put $match before $geoNear (not allowed)
Forget dot notation for filtering joined fields: "customer.tier"
Summary
Key Takeaways
Knowledge Unlocked
Five things to remember about $match
Use these points whenever you filter in aggregation pipelines.
5
Core concepts
🔍01
Like find()
Same syntax.
Basics
⚡02
Early stage
Index use.
Perf
🔗03
Pre-lookup
Shrink joins.
Pattern
📈04
$expr
Field compare.
Advanced
🔀05
$and / $or
Compound logic.
Operators
❓ Frequently Asked Questions
$match filters documents in an aggregation pipeline—keeping only those that satisfy the query conditions you specify. It uses the same query syntax as find(), including comparison operators, $in, $regex, $and, and $or.
As early as possible—typically first or right after stages that cannot be reordered. Early $match reduces documents flowing through expensive stages like $lookup, $group, and $sort, and lets MongoDB use indexes on matched fields.
The query syntax is identical. db.collection.find({ status: "active" }) equals [{ $match: { status: "active" } }] as the first aggregation stage. $match inside a pipeline can appear multiple times at different points.
Yes. $expr allows aggregation expressions in the query—e.g. { $match: { $expr: { $gte: ["$price", "$cost"] } } } compares two fields on the same document. Useful when the filter depends on computed or cross-field logic.
Yes. MongoDB may coalesce adjacent $match stages into one internally. You can also $match before $lookup to filter input, then $match again after $lookup to filter on joined fields.
Standard query operators: $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $and, $or, $not, $exists, $regex, $elemMatch, $type, $text (with text index), and $expr for aggregation expressions.
Did you know?
MongoDB can coalesce consecutive $match stages into a single filter for efficiency. When the first stage is $match and uses indexed fields, the aggregation planner may produce the same IXSCAN plan as an equivalent find(). See the official $match docs.