$redact is a MongoDB aggregation stage that restricts document content based on an expression. At each level, the expression must resolve to $$KEEP, $$PRUNE, or $$DESCEND.
Remember
$redact = conditional keep / prune / descend
{ $redact: <expression → $$KEEP | $$PRUNE | $$DESCEND> }
$$KEEP → keep this level; stop inspecting nested content
$$PRUNE → remove this level entirely
$$DESCEND → keep this level; check nested docs/arrays next
Usually wrapped in $cond
vs $match → whole document vs nested levels
vs $project → fixed field list vs access rules
Add $redact with a $cond that returns $$KEEP, $$PRUNE, or $$DESCEND.
Choose $$DESCEND when nested levels need their own checks; use $$KEEP when the whole subtree is allowed as-is.
Piece
Required?
Role
Expression
Yes
Must resolve to $$KEEP, $$PRUNE, or $$DESCEND
$cond
Common
Maps a boolean to those three outcomes
Access field
Usual
level, tags, or similar on each level
Pseudocode
Pseudocode
function redact(node):
decision = evaluate(expression on node)
if decision is $$PRUNE:
remove this node
else if decision is $$KEEP:
keep this node as-is (no nested checks)
else if decision is $$DESCEND:
keep this node's fields
for each embedded document / array element:
redact(child)
Reference
Cheat Sheet
Keep these facts handy while writing access rules.
Root and Overview pass. Both share a tag with the user, so $$DESCEND keeps them.
Budget is pruned. Tags ["TK"] do not intersect, so that subsection is removed.
Edge Cases & Pitfalls
Check these before using $redact in production pipelines.
return
Expression must return a system variable
The result must be $$KEEP, $$PRUNE, or $$DESCEND—not true/false alone.
prune
$$PRUNE drops nested content too
Pruning a parent removes children even if a child would have been allowed. Choose $$DESCEND when nested levels need separate checks.
keep
$$KEEP skips nested inspection
If you $$KEEP a parent, nested restricted fields are not re-checked. Use $$DESCEND when children may differ.
missing
Missing tags / level fields
If tags or level is missing, your condition may fail or prune unexpectedly. Design a default branch (often $$PRUNE for safety).
Analysis
Performance Notes
Approach
Cost
Best for
$match then $redact
Filter first
Fewer documents to inspect
$$DESCEND deep trees
Per nested level
Fine-grained nested security
$$KEEP / early prune
Stops recursion
Shallow decisions when safe
Prefer $match early. $redact with $$DESCEND walks nested structure, so keep documents and nesting as shallow as your security model allows.
Remember
Key Takeaways
Role: restrict content with $$KEEP, $$PRUNE, or $$DESCEND.
$$DESCEND: keep this level and check nested content.
Common pattern:$cond on level or tags.
vs $match / $project: nested access rules vs whole-doc filter vs fixed field lists.
One line:$redact answers “at this level, keep, prune, or keep looking?”
Frequently Asked Questions
$redact restricts whole documents or nested content based on an expression that resolves to $$KEEP, $$PRUNE, or $$DESCEND. It can evaluate access at each nested level—ideal for tag-based or level-based security.
$$KEEP keeps the current level and stops inspecting nested content. $$PRUNE removes the current level entirely. $$DESCEND keeps the current level but continues evaluating embedded documents and array elements inside it.
$match keeps or drops entire documents. $redact can prune nested subdocuments while keeping the parent—for example, hide a confidential section inside a report. Use $match first to narrow the set, then $redact for fine-grained trimming.
$project shapes fields with fixed include/exclude rules at one level. $redact applies a condition per level and can recurse with $$DESCEND. Use $project for simple column selection; use $redact for role-based or tag-based nested redaction.
Use $$DESCEND when the current level is allowed, but nested subdocuments may have different access rules—for example, a report the user can see, with sections that need separate checks.
Any expression that resolves to $$KEEP, $$PRUNE, or $$DESCEND—most often $cond. Tag checks commonly use $setIntersection with $size; level checks use $eq or comparisons on a level field.