MongoDB $expr Operator

Beginner
5 min read
Updated: Oct 2026
3 examples
Live preview

What Is $expr?

$expr is a MongoDB query operator that lets you run aggregation expressions inside find() and $match. Use it when a normal filter cannot compare two fields on the same document.

Remember
$expr = aggregation expression inside a query filter

{ $expr: <aggregation expression> }

// classic use — compare two fields:
{ $expr: { $gt: [ "$spent", "$budget" ] } }

input  → any aggregation expression (usually boolean)
output → document matches when expression is true

inside $expr → fields need "$field" (dollar prefix)
outside $expr → normal query: { budget: { $gt: 100 } }
NeedPrefer
Field vs constantNormal query ({ spent: { $gt: 100 } })
Field vs field$expr + $gt / $eq
Math or date logic in a filter$expr with aggregation operators

How to Use $expr in MongoDB

Wrap an aggregation expression that returns true or false, then use it in find() or $match.

  1. Decide the condition (for example: spent > budget).
  2. Write it as an aggregation expression: { $gt: [ "$spent", "$budget" ] }.
  3. Wrap with $expr: { $expr: { $gt: [ "$spent", "$budget" ] } }.
  4. Pass that object to find() or to $match.
PieceRequired?Role
Aggregation expressionYesUsually a boolean condition
"$field" refsWhen reading fieldsDollar-prefixed field paths
Result—Document kept when expression is true

Pseudocode

Pseudocode
for each document:
    value = evaluate(aggregationExpression)  // using this document's fields
    if value is true:
        keep document
    else:
        skip document

Cheat Sheet

Keep these facts handy while writing field-to-field filters.

TopicDetail
TypeQuery operator (works in find and $match)
Syntax{ $expr: { $gt: [ "$a", "$b" ] } }
Field refs"$spent", "$budget" (with $)
Common ops$gt, $eq, $lt, $multiply
Wherefind() filter or $match
Sample checkspent 120, budget 100 → $gt → match

Live Preview

Simulate $expr: { $gt: [ "$spent", "$budget" ] } for a few orders.

Simulates field-to-field $gt inside $expr (same idea as the mongosh examples below).

Result
item   = "Kit"
spent  = 120
budget = 100

$expr: { $gt: [ "$spent", "$budget" ] }
→ 120 > 100 → true

match ✓

Worked Walkthrough — Spent Over Budget

An order has spent: 120 and budget: 100. We want documents where spent is greater than budget.

Calculation
Document:
  { item: "Kit", spent: 120, budget: 100 }

Normal query CANNOT do:
  { spent: { $gt: "$budget" } }   ✗  // treats "$budget" as a string literal

With $expr:
  { $expr: { $gt: [ "$spent", "$budget" ] } }

Step by step:
  "$spent"  → 120
  "$budget" → 100
  $gt(120, 100) → true

Result:
  document matches ✓

MongoDB Examples

We use a small orders collection. First we store sample rows, then we find over-budget orders with $expr, then we filter with arithmetic inside $match. Run these in mongosh after inserting the sample data once.

Example 1 — Sample Data

Imagine three orders: Kit went over budget, Desk stayed under, and Lamp matched the budget exactly.

mongosh
db.orders.insertMany([
  { _id: 1, item: "Kit",  spent: 120, budget: 100 },
  { _id: 2, item: "Desk", spent: 80,  budget: 100 },
  { _id: 3, item: "Lamp", spent: 100, budget: 100 }
]);

How the Data Is Stored in MongoDB

After the insert, MongoDB keeps three documents in orders. Run find() to see them:

mongosh
db.orders.find()

Read each document like this:

Field in MongoDBMeaning
itemOrder name
spentAmount actually used
budgetAllowed amount
Picture
Kit:  spent 120  vs  budget 100  → over
Desk: spent  80  vs  budget 100  → under
Lamp: spent 100  vs  budget 100  → equal

$expr lets you ask: spent > budget?

How It Works

  1. Insert once. insertMany adds three order documents to orders.
  2. Two number fields. Each row has spent and budget so we can compare them next.
  3. find() shows the same shape. What you inserted is what you get back—ready for $expr.

Example 2 — Compare Fields

Goal: find orders where spent is greater than budget.

mongosh
db.orders.find({
  $expr: { $gt: [ "$spent", "$budget" ] }
});

How It Works

  1. Read both fields. "$spent" and "$budget" pull values from the current document.
  2. Compare with $gt. Aggregation-style $gt takes an array of two expressions.
  3. Keep true rows only. Kit is 120 > 100 → match. Desk and Lamp are not greater → skipped.
  4. Works in find(). No pipeline required for this filter.

Example 3 — Math in $match

Keep orders where spent is greater than 90% of budget ((budget × 0.9). That needs math inside the filter.

mongosh
db.orders.aggregate([
  {
    $match: {
      $expr: {
        $gt: [
          "$spent",
          { $multiply: [ "$budget", 0.9 ] }
        ]
      }
    }
  },
  {
    $project: {
      item: 1,
      spent: 1,
      budget: 1,
      threshold: { $multiply: [ "$budget", 0.9 ] }
    }
  }
]);

How It Works

  1. Compute a threshold. $multiply turns budget into budget × 0.9.
  2. Compare inside $expr. Aggregation math belongs inside $expr when used in $match.
  3. Check each row. Kit: 120 > 90 ✓. Desk: 80 > 90 ✗. Lamp: 100 > 90 ✓.
  4. Project a clean shape. Output shows item, spent, budget, and the threshold used.

Edge Cases & Pitfalls

Check these before using $expr in production queries.

syntax

Field refs need a $

Write "$spent", not "spent", inside the aggregation expression. Missing the dollar sign compares wrong values.

query

Normal $gt is not enough

{ spent: { $gt: "$budget" } } compares spent to the string "$budget". Use $expr for field-to-field compares.

type

Types must be comparable

Comparing a number to a string or null can give surprising results. Keep field types consistent.

index

Prefer normal filters when you can

If you only need field vs constant, use a normal query operator. Save $expr for field-to-field or expression logic.

lookup

$lookup + $expr

In a $lookup subpipeline $match, $expr can use let variables. Field-to-field compares still may not use indexes.

null

Missing fields can surprise you

If spent or budget is missing, comparisons may not match as you expect. Filter incomplete docs first when needed.

Performance Notes

ApproachCostBest for
Normal query operatorOften index-friendlyField vs constant
$expr compareExpression per documentField vs field
Store a computed flagWrite cost, cheaper readsVery frequent over-budget checks

Combine $expr with a normal predicate when possible (for example filter by status first, then compare fields). Keep the expression simple.

Key Takeaways

  • Role: run aggregation expressions inside find / $match.
  • Classic use: compare two fields on the same document.
  • Field refs: use "$field" inside the expression.
  • Tip: prefer normal operators for field vs constant.

One line: $expr answers “can this aggregation condition be true for this document?”—so you can compare fields in a query.

Frequently Asked Questions

$expr lets you use aggregation expression operators inside find() queries and $match stages. That means you can compare two fields on the same document, run arithmetic in a filter, or build conditions that normal query operators cannot express.
The syntax is { $expr: <aggregation expression> }. The expression should evaluate to a boolean. Example: { $expr: { $gt: [ "$spent", "$budget" ] } }.
Use $expr when you need to compare two fields (spent > budget), do math in a filter, or combine aggregation operators in one condition. Prefer normal query operators when you only compare a field to a constant.
Yes. Inside $expr, reference fields with a $ prefix as strings, like "$spent" or "$budget". That is aggregation expression syntax, not the usual query field name.
Yes. Put $expr inside a $match stage. The syntax matches find(). You can also use $expr with $lookup let variables in a subpipeline $match.
Often $expr forces more work than a simple field-to-constant filter. Prefer normal query operators when you only need field vs constant. Index use with $expr is limited; keep expressions simple and filter with normal predicates first when you can.

Next: $filter

Select matching elements from an array with an aggregation condition.

$filter tutorial →

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.

9 people found this page helpful