MongoDB $match Stage

Beginner
⏱️ 11 min read
📚 Updated: Jul 2026
🎯 5 Examples
Aggregation

What You’ll Learn

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.

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.

📝 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().
mongosh
db.products.aggregate([
  { $match: { category: "Electronics", inStock: true } },
  { $sort: { price: -1 } },
  { $limit: 10 }
])

⚡ Quick Reference

QuestionAnswer
What it doesFilters documents—passes matches, drops the rest
Query syntaxIdentical to find() filter documents
Best positionAs early as possible in the pipeline
Compare fields{ $match: { $expr: { … } } }
After $lookupFilter on joined array fields
vs $count$match filters; $count returns total only
Equality
{ status: "shipped" }

Exact match

Range
price: {
  $gte: 50, $lte: 200
}

Between

List
category: {
  $in: ["A","B"]
}

$in

Regex
name: {
  $regex: /^Pro/
}

Pattern

🧰 Common Query Patterns in $match

The stage itself has one argument—the query document. These are the operators you use most often inside it:

Field equality Basic

Match documents where a field equals a literal value—shorthand for $eq.

{ category: "Electronics" }
{ inStock: true }
Comparison operators Numeric / date

Filter ranges on numbers, dates, or comparable types.

{ price: { $gte: 100, $lt: 500 } }
{ createdAt: { $gte: ISODate("2025-01-01") } }
$and / $or Logic

Combine multiple conditions explicitly when implicit AND is not enough.

{ $or: [
  { tier: "gold" },
  { amount: { $gte: 1000 } }
]}
$expr Advanced

Compare two fields on the same document or use aggregation functions in the filter.

{ $expr: {
  $gt: ["$price", "$cost"]
}}

Examples Gallery

Sample products collection—basic filters, compound logic, regex search, $match after $lookup, and $expr for margin checks.

📚 Getting Started

Product catalog for filter labs.

Example 1 — Products collection

mongosh
db.products.drop()
db.products.insertMany([
  { name: "Widget Pro",    category: "Electronics", price: 299, cost: 180, inStock: true,  rating: 4.8 },
  { name: "Widget Lite",   category: "Electronics", price: 99,  cost: 55,  inStock: true,  rating: 4.2 },
  { name: "Office Chair",  category: "Furniture",   price: 450, cost: 280, inStock: false, rating: 4.5 },
  { name: "Desk Lamp",     category: "Furniture",   price: 65,  cost: 30,  inStock: true,  rating: 3.9 },
  { name: "USB Hub Pro",   category: "Electronics", price: 45,  cost: 50,  inStock: true,  rating: 4.0 },
  { name: "Monitor Stand", category: "Electronics", price: 120, cost: 70,  inStock: true,  rating: 4.6 }
])

db.products.createIndex({ category: 1, inStock: 1 })
db.products.createIndex({ price: 1 })

How It Works

Six products across two categories with price, cost, and inStock—enough variety to demonstrate equality, range, logic, and field-comparison filters.

Example 2 — In-stock electronics under $200

mongosh
db.products.aggregate([
  {
    $match: {
      category: "Electronics",
      inStock: true,
      price: { $lte: 200 }
    }
  },
  { $sort: { price: -1 } },
  {
    $project: {
      name: 1,
      price: 1,
      rating: 1,
      _id: 0
    }
  }
])

/* Matches: Widget Lite, Monitor Stand, USB Hub Pro
   (Electronics + inStock + price ≤ 200)
   Same as: find({ category: "Electronics", inStock: true, price: { $lte: 200 } })
*/

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

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)

mongosh
// Reuse customers + orders from $lookup tutorial
db.orders.aggregate([
  { $match: { status: "shipped" } },
  {
    $lookup: {
      from: "customers",
      localField: "customerId",
      foreignField: "_id",
      as: "customer"
    }
  },
  { $unwind: "$customer" },
  { $match: { "customer.tier": "gold" } },
  {
    $project: {
      product: 1,
      amount: 1,
      customerName: "$customer.name",
      tier: "$customer.tier",
      _id: 0
    }
  }
])

// First $match  → filter orders early (index-friendly)
// Second $match → filter on joined customer.tier field

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)

mongosh
db.products.aggregate([
  {
    $match: {
      inStock: true,
      $expr: { $gt: ["$price", "$cost"] }
    }
  },
  {
    $addFields: {
      margin: { $subtract: ["$price", "$cost"] }
    }
  },
  { $sort: { margin: -1 } },
  {
    $project: {
      name: 1,
      price: 1,
      cost: 1,
      margin: 1,
      _id: 0
    }
  }
])

// USB Hub Pro excluded (price 45 < cost 50)
// $expr compares fields on the SAME document
// Cannot express "price > cost" without $expr

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.

🧠 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.

📝 Notes

  • $match query syntax is identical to find() filter documents.
  • Place $match as 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.
  • Previous topic: $lookup. Next: $merge.

Conclusion

$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.

💡 Best Practices

✅ Do

  • 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"

Key Takeaways

Knowledge Unlocked

Five things to remember about $match

Use these points whenever you filter in aggregation pipelines.

5
Core concepts
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.

Continue the Stages Series

Filter with $match, then persist results with $merge.

Next: $merge →

About the author

Mari Selvan M P
Mari Selvan M P 🔗

Developer, cloud engineer, and technical writer

  • Experience 12 years building web and cloud systems
  • Focus Full Stack Development, AWS, and Developer Education

I write practical tutorials so students and working developers can learn by doing—from databases and APIs to deployment on AWS.

5 people found this page helpful