MongoDB $facet Stage

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

What You’ll Learn

The $facet stage runs several sub-pipelines in parallel on the same input documents—returning one document with multiple result arrays, like a dashboard with filters, charts, and a paginated table from a single query.

01

Parallel pipes

Many views, one pass.

02

Named outputs

Each key → array.

03

Page + count

Classic API pattern.

04

Faceted search

Tags, price, year.

05

$match first

Index-friendly.

06

Memory limits

100 MB per facet.

Definition and Usage

$facet is an aggregation stage that fans out input documents into multiple independent pipelines. MongoDB reads the input set once, runs every sub-pipeline, and merges results into a single output document.

💡
Beginner tip

Think of $facet like splitting one spreadsheet into several pivot tables at the same time. E-commerce sites use it for sidebar filters (category counts, price ranges) while also returning the product list—without three separate database round trips.

Use $facet for admin dashboards, faceted product search, and pagination metadata. Pair sub-pipelines with $sortByCount, $bucket, and $count.

📝 Syntax

$facet maps output field names to arrays of pipeline stages:

mongosh
{
  $facet: {
    <outputField1>: [ <stage1>, <stage2>, ... ],
    <outputField2>: [ <stage1>, <stage2>, ... ],
    ...
  }
}

Syntax Rules

  • Output fields — each key becomes a field on the single result document; value is always an array of result docs from that sub-pipeline.
  • Same input — every sub-pipeline receives identical documents from stages before $facet.
  • Independent — sub-pipelines cannot use each other’s output within the same $facet.
  • Forbidden stages — no nested $facet, $out, $merge, $geoNear, $collStats, or Atlas Search stages inside sub-pipelines.
  • Memory — each sub-pipeline output is capped at 100 MB; final document must stay under 16 MB BSON.
  • Indexes — place $match before $facet so filters can use indexes.
mongosh
db.products.aggregate([
  { $match: { inStock: true } },
  {
    $facet: {
      topSellers: [
        { $sort: { sold: -1 } },
        { $limit: 5 }
      ],
      byCategory: [
        { $sortByCount: "$category" }
      ]
    }
  }
])

⚡ Quick Reference

QuestionAnswer
Output shapeOne document: { facetA: [...], facetB: [...] }
Input readsDocuments fetched once before $facet
Sub-pipeline valueArray of stages (mini-pipeline)
Pagination patternitems: [$skip, $limit] + meta: [$count]
Common facet stages$sortByCount, $bucket, $bucketAuto
First stage?Avoid—causes COLLSCAN; $match first
Two facets
$facet: {
  list: [{ $limit: 10 }],
  stats: [{ $count: "n" }]
}

Dual output

Tag counts
[{ $unwind: "$tags" },
 { $sortByCount: "$tags" }]

Faceted filter

Price buckets
[{ $bucket: {
  groupBy: "$price",
  boundaries: [0,50,100]
}}]

Histogram facet

After facet
{ $project: {
  items: "$page.items"
}}

Reshape output

🧰 Parameters

The $facet object has one entry per sub-pipeline:

output field name Key

Name of the array field in the result document. Choose descriptive names: paginatedItems, categoryCounts.

topProducts: [ … ]
priceRanges: [ … ]
sub-pipeline Value

Array of aggregation stages executed in order on the shared input set.

[{ $match: … }, { $group: … }]
shared input Implicit

All sub-pipelines see the same documents output by stages before $facet.

$match → $facet → 3 branches
size limits Important

100 MB max per sub-pipeline result; 16 MB max for the final BSON document.

Use $limit in heavy facets

Examples Gallery

Sample product catalog—parallel summaries, pagination with total count, multi-dimensional faceted search, and index-friendly filtering.

📚 Getting Started

Sample products and a basic two-facet pipeline.

Example 1 — Sample products collection

mongosh
db.products.insertMany([
  { title: "Wireless Mouse", category: "Electronics", price: 25,  year: 2024, tags: ["wireless", "office"], sold: 120 },
  { title: "Mechanical Keyboard", category: "Electronics", price: 89, year: 2023, tags: ["mechanical", "office"], sold: 85 },
  { title: "Desk Lamp", category: "Home", price: 35, year: 2024, tags: ["lighting", "office"], sold: 60 },
  { title: "Monitor 27in", category: "Electronics", price: 220, year: 2023, tags: ["display", "office"], sold: 45 },
  { title: "Notebook Set", category: "Stationery", price: 12, year: 2024, tags: ["paper", "school"], sold: 200 },
  { title: "Webcam HD", category: "Electronics", price: 55, year: 2024, tags: ["wireless", "video"], sold: 95 }
])

How It Works

Six products with category, price, year, tags, and sold count—enough for faceted filters and ranking examples below.

Example 2 — Top sellers and category breakdown

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

/* Result (one document):
{
  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 }
  ]
} */

How It Works

Both sub-pipelines read all six products independently. MongoDB returns one document containing both result arrays—your API can map them directly to UI widgets.

📈 Practical Patterns

Pagination, multi-faceted search, and performance tips.

Example 3 — Paginated list + total count (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" }
      ]
    }
  }
])

/* {
  items: [
    { title: "Wireless Mouse", price: 25 },
    { title: "Webcam HD", price: 55 }
  ],
  meta: [ { total: 4 } ]   // 4 Electronics products
} */

How It Works

This is the most common production use of $facet. The items branch paginates; the meta branch counts—all on the same filtered Electronics set from $match.

Example 4 — Multi-faceted search (tags, price, year)

mongosh
db.products.aggregate([
  {
    $facet: {
      categorizedByTags: [
        { $unwind: "$tags" },
        { $sortByCount: "$tags" }
      ],
      categorizedByPrice: [
        {
          $bucket: {
            groupBy: "$price",
            boundaries: [0, 30, 100, 250],
            default: "Premium",
            output: {
              count: { $sum: 1 },
              titles: { $push: "$title" }
            }
          }
        }
      ],
      categorizedByYear: [
        {
          $bucketAuto: {
            groupBy: "$year",
            buckets: 3
          }
        }
      ]
    }
  }
])

/* One document with three facet arrays —
   sidebar filter counts for an e-commerce UI. */

How It Works

Matches MongoDB’s official artwork example pattern. Retail search UIs render each facet array as checkbox filters while the main product grid uses a separate query or another facet branch.

Example 5 — $match before $facet for index use

mongosh
// ✓ Good — $match first (can use index on category)
db.products.aggregate([
  { $match: { category: "Electronics", price: { $lte: 100 } } },
  {
    $facet: {
      products: [
        { $sort: { sold: -1 } },
        { $limit: 5 }
      ],
      avgPrice: [
        { $group: { _id: null, avg: { $avg: "$price" } } }
      ]
    }
  }
])

// ✗ Avoid as first stage — $facet alone scans entire collection
db.products.aggregate([
  {
    $facet: {
      all: [{ $count: "total" }]
    }
  }
])

How It Works

Filter early so $facet processes a smaller document set. Both sub-pipelines inherit the filtered Electronics under $100 subset.

🧠 How $facet Works

1

Input documents arrive

Prior stages (often $match) produce the shared document set for $facet.

Input
2

Sub-pipelines run in parallel

Each named branch executes its stage array independently on the same input.

Fan-out
3

Arrays collected

Each sub-pipeline’s results become an array stored under its output field name.

Merge
=

One facet document

API receives every summary in a single response—ready for UI binding.

📝 Notes

  • Each sub-pipeline result is limited to 100 MB; allowDiskUse does not apply inside $facet.
  • The final output document must fit within the 16 MB BSON document size limit.
  • Nested $facet inside a sub-pipeline is not allowed.
  • Sub-pipelines cannot include $out, $merge, or $geoNear.
  • Add stages after $facet to reshape or pick fields from facet arrays.
  • Previous topic: $currentOp. Next: $geoNear.

Conclusion

$facet is MongoDB’s built-in way to run parallel aggregations on one filtered dataset. Name your output fields, define sub-pipeline stage arrays, and return dashboards or paginated APIs in a single query.

Filter with $match first, use $limit in heavy facets, and remember memory caps. Next: $geoNear for location-based pipelines.

💡 Best Practices

✅ Do

  • Place $match before $facet for index use and smaller input
  • Use $facet for page rows + total count in one API call
  • Add $limit to sub-pipelines that could return huge arrays
  • Name facet fields to match your API response schema
  • Combine $sortByCount, $bucket for search sidebar filters

❌ Don’t

  • Start pipelines with $facet when the collection is large
  • Nested $facet or use forbidden stages inside sub-pipelines
  • Assume sub-pipelines can pass data to each other within one $facet
  • Return unbounded arrays that exceed 100 MB or 16 MB BSON limits
  • Replace $lookup joins with $facet—different purposes

Key Takeaways

Knowledge Unlocked

Five things to remember about $facet

Use these points whenever you build multi-summary pipelines.

5
Core concepts
📦 02

Named arrays

One doc out.

Output
📄 03

Page + count

API pattern.

Pattern
🔍 04

$match first

Index friendly.

Perf
⚠️ 05

100 MB cap

Per sub-pipe.

Limit

❓ Frequently Asked Questions

$facet runs multiple independent aggregation sub-pipelines on the same set of input documents in one stage. Each sub-pipeline's results are stored as an array in a named output field—ideal for dashboards that need several summaries from one filtered dataset.
{ $facet: { fieldName1: [ stage1, stage2, … ], fieldName2: [ … ] } }. Each key is the output field name; each value is an array of pipeline stages. $facet returns exactly one document containing all facet arrays.
No. All sub-pipelines receive the same input documents from prior stages, but one sub-pipeline's output cannot feed another inside the same $facet. Chain additional stages after $facet and reference a facet field if you need further processing.
Restricted stages include $facet (nested), $out, $merge, $geoNear, $collStats, $indexStats, $planCacheStats, $search, $searchMeta, and $vectorSearch.
Use $facet with two sub-pipelines on the same $match input: items: [{ $skip: N }, { $limit: M }] and meta: [{ $count: "total" }]. Both run in parallel—one round trip for page + count.
If $facet is the first stage, it performs a collection scan. Put $match or $sort before $facet so earlier stages can use indexes; $facet then processes the filtered set without triggering its own COLLSCAN.
Did you know?

The name $facet comes from faceted search in e-commerce—showing counts per filter dimension (brand, price range, size) so shoppers can narrow results. MongoDB reads input documents once, so three facet branches cost less than three separate full-collection scans. See the official $facet docs.

Continue the Stages Series

Run parallel summaries with $facet, then explore location queries with $geoNear.

Next: $geoNear →

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