MongoDB $facet Stage

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

What Is $facet?

$facet is a MongoDB aggregation stage that runs several sub-pipelines on the same input and returns one document with a named array for each branch—like a dashboard from a single query.

Remember
$facet = "one input → many summaries"

{
  $facet: {
    topSellers: [ { $sort: … }, { $limit: 3 } ],
    byCategory: [ { $sortByCount: "$category" } ]
  }
}
// → one document: { topSellers: […], byCategory: […] }

same input for every branch
branches do not share outputs
returns exactly one document
common: page items + total count

Filter with $match BEFORE $facet when you can
UsePrefer
Several summaries, one query$facet
One summary / one group key$group
Only a filtered total$count

How to Use $facet in MongoDB

Filter once, then fan out into named sub-pipelines.

  1. Optionally filter with $match first (helps indexes).
  2. Add $facet with one key per branch (each value is a stage array).
  3. Inside each branch, use normal stages ($sort, $limit, $count, …).
  4. Read the single result document—each field is that branch’s array.
PieceRequired?Role
Facet field nameYesOutput key for that branch’s array
Sub-pipeline arrayYesStages that build that facet
Prior $matchNoShared filter for all branches

Pseudocode

Pseudocode
input = documents from prior stages

result = {}
for each (name, pipeline) in $facet:
  result[name] = run(pipeline, copy of input)
  // pipelines are independent

emit exactly one document: result
// e.g. { topSellers: […], byCategory: […] }

Cheat Sheet

Keep these facts handy while building dashboard queries.

TopicDetail
TypeAggregation stage
OutputExactly one document
Each fieldArray of that branch’s results
InputSame docs for every branch
IndependenceBranches cannot feed each other
Forbidden inside$out, $merge, nested $facet, $geoNear, …
Classic patternitems + meta ($count)

Live Preview

Simulate two common $facet shapes on the sample products (teaching model).

Six sample products. Page mode uses category Electronics, page size 2.

Result
{
  topSellers: [
    { title: "Notebook Set", sold: 200 },
    { title: "Wireless Mouse", sold: 120 },
    { title: "Webcam HD", sold: 95 }
  ],
  byCategory: [
    { _id: "Electronics", count: 4 },
    { _id: "Home", count: 1 },
    { _id: "Stationery", count: 1 }
  ]
}

Worked Walkthrough — Two Branches

Six products. Goal: top 3 by sold, plus a category count, in one stage.

Calculation
Input (6 products) ──┬──► topSellers: sort sold DESC → limit 3
                     │      Notebook Set 200, Mouse 120, Webcam 95
                     │
                     └──► byCategory: sortByCount(category)
                            Electronics: 4, Home: 1, Stationery: 1

Emit ONE document:
  { topSellers: […], byCategory: […] }

MongoDB Examples

We use a small products collection. First build a mini dashboard, then return a page of rows plus a total count. Run these in mongosh after inserting the sample data once.

Example 1 — Sample Data

Six products with category, price, and sold count—enough for ranking and facet counts.

mongosh
db.products.drop()
db.products.insertMany([
  { title: "Wireless Mouse", category: "Electronics", price: 25,  sold: 120 },
  { title: "Mechanical Keyboard", category: "Electronics", price: 89, sold: 85 },
  { title: "Desk Lamp", category: "Home", price: 35, sold: 60 },
  { title: "Monitor 27in", category: "Electronics", price: 220, sold: 45 },
  { title: "Notebook Set", category: "Stationery", price: 12, sold: 200 },
  { title: "Webcam HD", category: "Electronics", price: 55, sold: 95 }
])

How the Data Is Stored in MongoDB

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

mongosh
db.products.find()

Read the important fields like this:

FieldExamplePlain English
category"Electronics"Group key for facet counts
sold200Units sold (ranking)
price25Used when sorting a page of rows

How It Works

  1. Reset once. drop() keeps demos repeatable.
  2. Insert six products. Mixed categories feed the facet counts.
  3. Ready for branches. Next examples fan this set into multiple summaries.

Example 2 — Two Facets

Goal: top 3 sellers and a category breakdown in one stage—two UI widgets, one query.

mongosh
db.products.aggregate([
  {
    $facet: {
      topSellers: [
        { $sort: { sold: -1 } },
        { $limit: 3 },
        { $project: { title: 1, sold: 1, _id: 0 } }
      ],
      byCategory: [
        { $sortByCount: "$category" }
      ]
    }
  }
])

How It Works

  1. Same input. Both branches see all six products.
  2. Branch A ranks. Sort by sold, keep three titles.
  3. Branch B counts. $sortByCount groups by category.
  4. One document out. Your API maps each array to a widget.

Example 3 — Page + Count

Goal: return a page of Electronics products and the full match total in one round trip—the classic API pattern.

mongosh
const page = 1
const pageSize = 2
const skip = (page - 1) * pageSize

db.products.aggregate([
  { $match: { category: "Electronics" } },
  {
    $facet: {
      items: [
        { $sort: { price: 1 } },
        { $skip: skip },
        { $limit: pageSize },
        { $project: { title: 1, price: 1, _id: 0 } }
      ],
      meta: [
        { $count: "total" }
      ]
    }
  }
])

How It Works

  1. Filter once. $match keeps four Electronics products for both branches.
  2. items branch. Sort by price, skip/limit for page 1 (2 rows).
  3. meta branch. $count returns the full filtered total (4).
  4. One round trip. You avoid a separate count query for pagination UI.

Edge Cases & Pitfalls

Check these before shipping dashboard pipelines with $facet.

share

Branches cannot feed each other

Every sub-pipeline starts from the same input. To use one result in another step, add stages after $facet.

forbid

Some stages are not allowed inside

No nested $facet, $out, $merge, $geoNear, $collStats, or Atlas Search stages inside a facet branch.

mem

Each branch may hold a lot of data

Large unsorted inputs can pressure memory. Filter early and keep branch outputs small with $limit / $project.

index

Put $match before $facet

A leading $facet scans the collection. Filter first so indexes can shrink the shared input.

Performance Notes

ApproachCostBest for
$match then $facetOne filtered fan-outDashboards, page+count
Two separate aggregatesTwo round tripsSimple scripts only
$facet first (no filter)Full collection into branchesAvoid on large collections

Prefer one filtered $facet over repeating the same $match in several client queries. Keep each branch’s output lean.

Key Takeaways

  • Role: many summaries from one shared input.
  • Output: exactly one document of named arrays.
  • Pattern: items + meta for page and total.
  • Tip: filter before $facet; keep branches independent.

One line: $facet answers “give me several views of this same dataset at once.”

Frequently Asked Questions

$facet runs multiple independent aggregation sub-pipelines on the same input documents in one stage. Each sub-pipeline’s results become an array under a named field. Ideal for dashboards that need several summaries from one filtered set.
{ $facet: { fieldName1: [ stage1, stage2, … ], fieldName2: [ … ] } }. Each key is an output field name; each value is a pipeline array. $facet returns exactly one document containing all facet arrays.
No. All sub-pipelines receive the same input from stages before $facet, but one branch’s output cannot feed another inside the same $facet. Process further after $facet if needed.
Restricted stages include nested $facet, $out, $merge, $geoNear, $collStats, $indexStats, $planCacheStats, and Atlas Search stages such as $search.
Use $facet with items: [{ $skip }, { $limit }] and meta: [{ $count: "total" }] on the same $match input. One round trip for page + count.
If $facet is first, it scans the collection. Put $match (or a selective filter) before $facet so earlier stages can use indexes; $facet then works on the filtered set.

Next: $geoNear

Find documents closest to a point—must be the first stage in the pipeline.

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