Get Started Free

Pipeline Basics

A pipeline is an array of stages; each stage transforms the documents and passes them to the next.

aggregate()
db.orders.aggregate([
  { $match: { status: "shipped" } },
  { $group: { _id: "$region", total: { $sum: "$amount" } } },
  { $sort:  { total: -1 } }
])
Stages run top-to-bottom. Put $match and $limit early to reduce work.
$match, filter
{ $match: {
    status: "active",
    amount: { $gte: 100 },
    tags: { $in: ["vip", "new"] }
} }
Same query syntax as find(). Runs before other stages so indexes can be used.
$count
{ $count: "numDocs" }
Outputs a single document: { numDocs: N }.

Reshaping Stages

Select, rename, compute, and restructure fields.

$project
{ $project: {
    _id: 0,
    name: 1,
    year: { $year: "$createdAt" },
    fullName: { $concat: ["$first", " ", "$last"] }
} }
1 = include, 0 = exclude. Can also compute new fields.
$addFields / $set
{ $addFields: {
    total: { $multiply: ["$price", "$qty"] }
} }
$set is an alias for $addFields. Keeps existing fields.
$unset
{ $unset: ["password", "internal.notes"] }
Remove one or more fields.
$replaceRoot
{ $replaceRoot: { newRoot: "$profile" } }
Promote an embedded document to the top level.

Grouping & Accumulators

$group buckets documents by a key and rolls them up with accumulators.

$group
{ $group: {
    _id: "$category",
    count:  { $sum: 1 },
    revenue:{ $sum: "$amount" },
    avg:    { $avg: "$amount" }
} }
_id is the group key; use null to aggregate everything.
Accumulators
$sum   $avg   $min   $max
$first $last
$push       // array of values
$addToSet   // array of unique values
$count      // (as a stage)
Use $push/$addToSet to collect values per group.
Group by multiple keys
{ $group: {
    _id: { region: "$region", month: { $month: "$date" } },
    total: { $sum: "$amount" }
} }
Compose a key from several fields with a sub-document.

Arrays & Joins

Flatten arrays and pull in related collections.

$unwind
{ $unwind: "$items" }
// or, keep empty arrays:
{ $unwind: {
    path: "$items",
    preserveNullAndEmptyArrays: true
} }
Outputs one document per array element.
$lookup, join
{ $lookup: {
    from: "customers",
    localField: "customerId",
    foreignField: "_id",
    as: "customer"
} }
Left outer join. Result customer is an array, often followed by $unwind.
$lookup with pipeline
{ $lookup: {
    from: "orders",
    let: { cid: "$_id" },
    pipeline: [
      { $match: { $expr: { $eq: ["$custId", "$$cid"] } } },
      { $project: { total: 1 } }
    ],
    as: "orders"
} }
Correlated sub-pipeline for filtered / shaped joins.

Sort, Page & Sample

Order and slice the result set.

$sort / $limit / $skip
{ $sort:  { createdAt: -1 } }   // -1 desc, 1 asc
{ $limit: 20 }
{ $skip:  40 }
For paging, sort on a stable, indexed field.
$facet, multi-pipeline
{ $facet: {
    rows:  [ { $skip: 0 }, { $limit: 20 } ],
    total: [ { $count: "n" } ]
} }
Run several sub-pipelines over the same input, great for paged results + counts.
$bucket
{ $bucket: {
    groupBy: "$price",
    boundaries: [0, 50, 100, 200],
    default: "200+",
    output: { count: { $sum: 1 } }
} }
Group into ranges. $bucketAuto picks boundaries for you.

Expression Operators

Use inside $project / $addFields / $group. Prefix field names with $.

Conditional
{ $cond: [ { $gte: ["$score", 60] }, "pass", "fail" ] }
{ $ifNull: [ "$nickname", "$name" ] }
{ $switch: { branches: [
    { case: { $gte: ["$n",90] }, then: "A" },
    { case: { $gte: ["$n",80] }, then: "B" }
  ], default: "C" } }
Arithmetic & comparison
$add $subtract $multiply $divide $mod
$eq $ne $gt $gte $lt $lte $cmp
$and $or $not
String
$concat  $toUpper  $toLower  $trim
$substrCP  $strLenCP  $split
$regexMatch: { input:"$x", regex:"^A" }
Date
$year $month $dayOfMonth $hour
$dateToString: { format:"%Y-%m-%d", date:"$createdAt" }
$dateTrunc: { date:"$ts", unit:"day" }
Array
$size  $arrayElemAt  $slice
$filter: { input:"$items", as:"i",
           cond:{ $gt:["$$i.qty",0] } }
$map $reduce $in $concatArrays

Common Recipes

Copy-paste starting points for frequent tasks.

Count by group, sorted
db.events.aggregate([
  { $group: { _id: "$type", n: { $sum: 1 } } },
  { $sort:  { n: -1 } }
])
Join + flatten + pick fields
db.orders.aggregate([
  { $lookup: { from:"customers", localField:"custId",
               foreignField:"_id", as:"c" } },
  { $unwind: "$c" },
  { $project: { total:1, name:"$c.name" } }
])
Paged result + total count
db.products.aggregate([
  { $match: { inStock: true } },
  { $facet: {
      data:  [ { $sort:{price:1} }, { $skip:0 }, { $limit:20 } ],
      count: [ { $count: "total" } ]
  } }
])
Running total (window)
db.sales.aggregate([
  { $setWindowFields: {
      sortBy: { date: 1 },
      output: { runningTotal: {
        $sum: "$amount",
        window: { documents: ["unbounded","current"] }
      } }
  } }
])
Requires MongoDB 5.0+.

More information about the MongoDB aggregation pipeline

The aggregation pipeline is MongoDB's framework for transforming and analyzing data. It's one of the most important skills for any MongoDB developer: it powers reporting, analytics, joins, grouping, and data reshaping that a simple find() query can't express. Understanding it well is the difference between pulling raw documents into your app and letting the database do the heavy lifting efficiently.

What is an aggregation pipeline?

A pipeline is an ordered array of stages. Each stage takes the stream of documents from the previous stage, transforms it, and passes the result to the next, much like Unix pipes. Common stages include $match (filter), $group (roll up), $project (reshape), $sort, $lookup (join), and $unwind (flatten arrays).

Why it matters

Frequently asked questions

What is the difference between find() and aggregate()?

find() retrieves and filters documents. aggregate() runs a multi-stage pipeline that can additionally group, join, reshape, and compute, anything from a simple filter to a full analytics query.

Which stage should come first?

Put $match and $limit as early as possible so the pipeline processes fewer documents and can use indexes. Filtering before $group or $lookup is the single biggest performance win.

Can the aggregation pipeline join collections?

Yes, $lookup performs a left outer join to another collection, and its pipeline form supports correlated, filtered joins.

VisuaLeaf

Build aggregations visually in VisuaLeaf

Skip memorizing stage syntax. VisuaLeaf's Aggregation Pipeline builder lets you assemble, preview, and tune pipelines stage by stage, then switch to code when you want. One client for MongoDB and SQL.

MongoDB & SQL in one client
Visual query & aggregation builders
Live visual schema diagrams
Charts, dashboards & query profiler
Connection manager for local, cloud & teams
Free Community Edition