The $project stage reshapes documents in a pipeline—keeping only the fields you need, dropping sensitive data, renaming columns, and computing new values. It is one of the most-used stages for API responses, reports, and pre-export cleanup.
01
Include (1)
Whitelist fields.
02
Exclude (0)
Drop fields.
03
Rename
new: "$old"
04
Expressions
Computed values.
05
After $group
Flatten _id.
06
vs $addFields
Reshape vs add.
Fundamentals
Definition and Usage
$project is a transformation stage that outputs a new document shape for each input document. You control which fields appear, what they are named, and how their values are computed.
💡
Beginner tip
Two common modes: Inclusion — list fields with 1: only these fields pass through (plus _id unless you set _id: 0). Exclusion — list fields with 0: everything except these fields passes through. Do not mix 1 and 0 on regular fields in the same $project.
Use $project before returning results to a client, before $out or $merge to strip internal fields, or after $group to rename _id into a readable column like region or category.
Foundation
📝 Syntax
$project maps output field names to inclusion flags, field paths, or expressions:
Five products with public fields (name, price) and internal fields (cost, internalSku)—enough to demonstrate inclusion, exclusion, and computed projections.
Example 2 — Include only public catalog fields (whitelist)
When you set fields to 1, MongoDB treats it as inclusion mode—only those fields (plus _id unless _id: 0) survive. Ideal for slim API payloads.
Example 3 — Exclude internal fields (blacklist)
mongosh
db.products.aggregate([
{
$project: {
cost: 0,
internalSku: 0
}
}
])
// Exclusion mode: everything EXCEPT cost and internalSku
// name, price, category, inStock, rating, _id all kept
// Use when you only need to hide a few sensitive fields
📤 Blacklist output:
All fields except cost, internalSku
_id still present (default in exclusion mode)
Cannot mix cost: 0 with name: 1 in same $project
How It Works
Exclusion mode (0) is faster to write when most fields are safe to expose. You cannot mix cost: 0 with name: 1 in the same stage—that triggers a MongoDB error.
📈 Practical Patterns
Rename, compute, and flatten grouped results.
Example 4 — Rename fields and compute margin + sale flag
Widget Pro: margin 119, isBudget false
USB Hub: margin -5, isBudget true
Expressions work alongside inclusion (category: 1)
How It Works
In inclusion-style $project, you can mix literal includes (category: 1), renames (productName: "$name"), and expressions. Fields not listed are omitted from output.
{ category: "Furniture", avgPrice: 257.5, productCount: 2, ... }
{ category: "Electronics", avgPrice: 147.25, productCount: 3, ... }
No raw _id object in final output
How It Works
After $group, the grouping key lives in _id. A follow-up $project maps category: "$_id" and sets _id: 0 so dashboards and exports get human-readable field names.
Compare
📋 $project vs related shaping tools
Tool / stage
Effect
Best for
$project
Reshape output—include, exclude, rename, compute
API responses, stripping fields, post-$group cleanup
$addFields / $set
Add fields; keep all existing fields by default
Adding a few computed columns without dropping others
$unset
Remove specific fields only
Dropping known fields without full reshape
find() projection
Simple include/exclude at query time
Basic reads without aggregation
$replaceRoot
Promote a nested doc to top level
Flattening embedded documents
🧠 How $project Works
1
Document arrives
Each document from the previous stage enters $project with its full field set.
Input
2
Fields evaluated
MongoDB applies inclusion/exclusion rules and evaluates expressions for each output key.
Transform
3
New shape built
A slimmer or renamed document is emitted—only the projected fields appear in output.
Output
=
📄
Shaped stream
Downstream stages and clients receive documents in the exact structure you defined.
Important
📝 Notes
Do not mix inclusion (1) and exclusion (0) on regular fields—except _id: 0 in inclusion mode.
_id is included by default in inclusion projections unless you set _id: 0.
Renaming uses a string path: newKey: "$oldKey"—not newKey: 1.
For adding fields without dropping others, prefer $addFields or $set.
Early $project can reduce memory in long pipelines by dropping large embedded arrays early.
$project is how you control what leaves a pipeline: whitelist public fields, hide secrets, rename keys for APIs, and compute derived columns—all in one stage.
Pair it with $match and $group for full analytics flows. When you only need to add fields, use $addFields. Next: $redact for conditional document trimming.
Use inclusion mode for strict public-field allowlists
Project after $group to rename _id into meaningful keys
Drop large unused fields early to save memory in long pipelines
Use $addFields when you only need one or two extra columns
❌ Don’t
Mix 1 and 0 on non-_id fields in one $project
Assume exclusion mode hides _id—it is kept unless _id: 0
Use $project when $addFields would be simpler (adding only)
Forget that unlisted fields disappear in inclusion mode
Expose cost, tokens, or PII—project them out before $out
Summary
Key Takeaways
Knowledge Unlocked
Five things to remember about $project
Use these points whenever you shape documents in aggregation pipelines.
5
Core concepts
📄01
Reshape
Control output.
Basics
✅02
1 vs 0
Include/exclude.
Modes
📝03
Rename
"$field" paths.
Pattern
⚙04
Expressions
Compute values.
Advanced
📊05
Post-$group
Flatten _id.
ETL
❓ Frequently Asked Questions
$project reshapes each document in a pipeline—keeping only the fields you specify, excluding others, renaming fields, and computing new values with aggregation expressions. It is the primary stage for controlling output shape before APIs, exports, or downstream stages.
Generally no—except for _id. You either whitelist fields with 1 (include these) or blacklist with 0 (exclude these). Mixing 1 and 0 on regular fields causes an error. _id can always be set to 0 even in inclusion mode.
$project replaces the document shape—you must list fields to keep (inclusion mode) or fields to drop (exclusion mode). $addFields adds fields while keeping all existing ones by default. Use $project when trimming output; use $addFields when adding a few computed columns.
Yes. Any field value can be an expression: { total: { $multiply: ["$price", "$qty"] } }, { label: { $concat: ["$first", " ", "$last"] } }, or { year: { $year: "$createdAt" } }.
Often after $match (to reduce payload early), after $group (to flatten _id into named fields), or before $out/$merge (to strip sensitive columns). Late $project trims data sent to clients; early $project can reduce work in heavy downstream stages.
Similar idea—both select fields—but $project runs inside aggregation and supports full expression syntax. find() projection is limited to include/exclude/rename via simpler syntax; $project can compute derived fields with $multiply, $cond, etc.
Did you know?
Placing $projectearly in a pipeline—right after $match—can significantly reduce memory when documents contain large arrays or embedded blobs you never use downstream. MongoDB still reads full documents from disk, but later stages process slimmer in-memory shapes. See the official $project docs.