MongoDB lookup Stage
The $lookup stage performs a left outer join, attaching matching documents from another collection onto each document in the pipeline.
The Basic $lookup Shape
$lookup takes four required fields in its simplest form: from names the collection to join against, localField names the field in the current pipeline's documents, foreignField names the matching field in the other collection, and as names the new array field where matched documents will be attached. Every document from the input keeps its original fields and gains one new array field, even if no match was found, in which case that array is simply empty.
Joining Orders to Customers
db.orders.aggregate([
{ $lookup: {
from: "customers",
localField: "customerId",
foreignField: "_id",
as: "customerInfo"
} }
])After this stage runs, each order document has a new customerInfo field containing an array. In this example it will typically hold zero or one matching customer document, since customerId usually maps one-to-one against a customer's _id.
Unwrapping the Joined Array
Because $lookup always produces an array, it's common to follow it with $unwind to flatten a single matching document out of that array, or with $project to pull one field out of position zero directly. Choose $unwind when you expect zero-or-many matches and want one output document per match; choose direct array indexing when you know the join is one-to-one.
Unwinding a One-to-One Join
db.orders.aggregate([
{ $lookup: {
from: "customers",
localField: "customerId",
foreignField: "_id",
as: "customerInfo"
} },
{ $unwind: "$customerInfo" },
{ $project: {
_id: 0,
orderId: "$_id",
customerName: "$customerInfo.name",
total: 1
} }
])Advanced $lookup with a Sub-Pipeline
A more powerful form of $lookup accepts a let clause to bring pipeline variables into scope, plus a pipeline array to run a full sub-aggregation against the foreign collection. This lets you filter, sort, or limit the joined documents rather than pulling in every match unconditionally.
Joining Only Recent Reviews per Product
db.products.aggregate([
{ $lookup: {
from: "reviews",
let: { productId: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$productId", "$$productId"] } } },
{ $sort: { createdAt: -1 } },
{ $limit: 3 }
],
as: "recentReviews"
} }
])