Mongo Query Builder
Write a MongoDB query without memorising the operators: build find filters and aggregation pipelines visually, then take them as mongosh, Node driver or PyMongo code — or paste the filter straight into Compass.
Query
Conditions
4Consecutive OR conditions form one group. The group is nested inside $and rather than flattened, because a top-level $or beside other fields is a different query from the one you described.
Filter
{
$and: [
{ status: "shipped" },
{ total: { $gte: 100 } },
{ $or: [
{ region: "EU" },
{ region: "UK" }
] }
]
}Code
db.orders.find(
{
$and: [
{ status: "shipped" },
{ total: { $gte: 100 } },
{ $or: [
{ region: "EU" },
{ region: "UK" }
] }
]
},
{ _id: 1, total: 1, status: 1 }
)
.sort({ total: -1 })
.limit(20)Built in your browser — nothing is uploaded, and no database is contacted. For SQL rather than MongoDB, use the SQL Query Builder.
About the Mongo Query Builder
This MongoDB query builder turns a set of conditions into a find filter or an aggregation pipeline, and emits it as mongosh, Node driver or PyMongo code. It is the direct counterpart of the SQL query builder on this site, and it carries that tool's governing rule across: a run of OR conditions is always grouped explicitly.
If you are here because you know what you want to match but not which operator expresses it, that is the case this is built for. Pick the field, pick a comparison, give it a value, and the filter appears — $gt and $lte for ranges, $in for a list of accepted values, $ne for exclusion, $exists for presence, $regex for partial text. You can read the operator off the output and learn it in passing rather than looking it up first.
The filter it produces is portable. It is the same document mongosh takes, the same one the Node and Python drivers take, and the same one you paste into the filter box in MongoDB Compass or the Data Explorer in Atlas — those two fields want a find filter and nothing else, so copying the find output and pasting it in works directly.
That rule is the reason this is an engine rather than a text template. In Mongo, a top-level $or sitting beside other fields does not mean what someone describing "A and B, or C" usually intends, and a document cannot hold the same key twice — so two conditions on the same field at the top level would silently drop one of them. Both cases are handled by nesting under $and, which is always correct and occasionally more verbose than a hand-written filter would be.
Injection works differently in Mongo than in SQL, and the difference misleads people. There is no string concatenation and no placeholder, which makes it easy to conclude the problem does not exist. It does: passing a request body straight into a filter lets a caller send {"password": {"$ne": null}} and match every document. The generated code assigns values in the query body rather than showing you how to splice in a request field, and the FAQ covers what to do about it.
Aggregation pipelines are built from the same conditions — $match reuses the filter you built, and $group, $sort, $limit, $lookup and $unwind stages can be added in order.
Warnings appear for the things that are legal, run fine, and are usually a mistake: an unfiltered deleteMany, an unanchored regex that cannot use an index, a field name containing a dollar sign.
- find, aggregate, countDocuments, updateMany and deleteMany
- Thirteen operators, including $in, $nin, $all, $exists, $regex, $size and $type
- AND / OR per condition, with OR runs grouped correctly rather than flattened
- Typed values — string, number, boolean, null, ObjectId, date, or raw passthrough
- Three targets: mongosh, the Node driver with await, and PyMongo with quoted keys and tuple sorts
- Aggregation stages: $match, $group, $sort, $limit, $lookup and $unwind
- Warnings for unfiltered destructive operations, unanchored regexes and operator-shaped field names
How to use it
- Pick the operation and name the collection.
- Add conditions. Each takes a field, an operator, a value and a value type — the type is what decides whether 42 is emitted as a number or a string.
- Use the AND / OR button between conditions to group them. Consecutive ORs form one group.
- For find, set the projection, sort, limit and skip.
- For aggregate, add stages in the order you want them to run. $match uses the conditions you built above.
- Switch the target to the driver you are using and copy the code.
Real-world use cases
Backend & API developers
Turn a set of filter conditions from a feature spec into a correctly nested find query, without hand-writing the $and/$or grouping that Mongo's document model requires.
Data engineers & analysts
Build an aggregation pipeline — $match, $group, $lookup, $sort — and get the equivalent Node or PyMongo code to drop straight into a script.
QA & test engineers
Reconstruct the exact filter conditions from a bug report to reproduce it, and check the same query in mongosh, Node and Python without translating by hand.
DevOps & on-call engineers
Build a scoped, filtered updateMany or deleteMany and see the unfiltered-operation warning before running anything destructive against production.
Full-stack developers
Keep query logic consistent across a Node service and a Python service by generating both from the same set of conditions instead of maintaining two hand-written versions.
Examples
Equality uses the shorthand
status equals shipped
{ status: "shipped" }Idiomatic Mongo, and what a reader expects. { status: { $eq: "shipped" } } is equivalent and nobody writes it.
An OR run is grouped, not flattened
status = shipped AND (region = EU OR region = UK)
{
$and: [
{ status: "shipped" },
{ $or: [{ region: "EU" }, { region: "UK" }] }
]
}Flattening the $or to the top level alongside status would change which documents match, silently.
Two conditions on one field
age >= 18 AND age < 65
{ $and: [{ age: { $gte: 18 } }, { age: { $lt: 65 } }] }A JSON object cannot hold age twice, so the naive filter drops the first condition. Under $and both survive.
Matching any of several values
status is one of shipped, delivered, returned
{ status: { $in: ["shipped", "delivered", "returned"] } }$in is the operator people most often reach for $or to imitate. Three $or branches on the same field are equivalent but longer, and $in is the one an index can use cleanly.
A filter you can paste into Compass
total > 100 AND status = shipped
{ total: { $gt: 100 }, status: "shipped" }Compass's filter box and Atlas's Data Explorer both take a find filter on its own — no db.collection.find() wrapper, no method chain. Copy the find output rather than the mongosh output for those two.
PyMongo needs quoted keys
sort by total descending
cursor = db["orders"].find({"status": "shipped"}).sort([("total", -1)])Bare keys are valid JavaScript and not valid Python, and PyMongo's sort takes a list of tuples rather than a document.
Common errors
| Message | Cause | Fix |
|---|---|---|
| The query returns every document | An empty filter. In Mongo, {} matches everything rather than nothing. | Add a condition. The builder warns on an unfiltered find with no limit, and warns more loudly on an unfiltered deleteMany. |
| unknown top level operator: $gte | An operator used where a field name belongs — { $gte: 18 } instead of { age: { $gte: 18 } }. | The builder always nests operators under a field, so copying its output avoids this. The warning about a $ in a field name catches the reverse mistake. |
| The query is slow and scans the whole collection | An unanchored $regex, a $ne or $nin, or a sort on an unindexed field. None of these can use an index efficiently. | Anchor regexes with ^ — the builder warns when you do not. Run .explain("executionStats") to confirm what the planner does. |
| Dates compare as strings | The value type was left as string, so the date was emitted quoted. | Set the type to date. Mongo stores dates as BSON dates, and a quoted ISO string will not compare correctly against them. |
| An ObjectId lookup finds nothing | The _id was passed as a string. A 24-character hex string is not equal to the ObjectId it represents. | Set the value type to ObjectId, which wraps it correctly for every target. |
Frequently asked questions
›How do I write a query in MongoDB Compass?
Compass's filter box takes a find filter document and nothing else — no db.collection.find() wrapper and no method chain, which is the mistake that produces its unhelpful parse error. Build the conditions above, copy the find output rather than the mongosh output, and paste it in. Sort, projection and limit have their own boxes in Compass under Options, so a query built here as a sort or limit belongs in those fields rather than in the filter.
›What is MongoDB's query language, and is it SQL?
No. MongoDB queries are documents, not statements: you describe the shape a matching document must have, using operators like $gt, $in and $regex as keys inside that shape. There is no SELECT, no WHERE clause and no string to parse. The practical consequence is that a query is data — you can build it, store it, pass it around and compose it programmatically, which a SQL string cannot do safely — and the practical cost is that the operators have to be learned, since nothing about the syntax hints at them.
›How do I combine conditions with AND?
Two conditions on different fields need nothing: { status: "shipped", total: { $gt: 100 } } is already an AND, because a document has to match every key. It is only when two conditions apply to the same field that you need an explicit $and — a JSON object cannot hold that key twice, so the naive version silently drops one. The builder above adds $and whenever it is required and leaves it out whenever it is not.
›Is MongoDB injection a real risk if queries are documents rather than strings?
Yes, and the document model is why people miss it. Passing a request body directly into a filter lets a caller supply an operator instead of a value — {"password": {"$ne": null}} matches every document, and no string was concatenated anywhere. Validate the type of every value before it reaches a filter, or use a schema layer that does. It is a different mistake from SQL injection with the same consequence.
›Why does it wrap OR conditions in $and instead of using a top-level $or?
Because they are different queries. A top-level $or beside other fields means "either this whole branch, or that one", not "those fields, and one of these". Nesting is always correct; the flat form is correct only in the special case where there is nothing else in the filter, which the builder does detect and use.
›What does the raw value type do?
Passes the text through untouched, for expressions the builder cannot model — $$NOW, a $expr fragment, or an aggregation variable. It is the only type that can produce invalid output, so it raises a warning every time it is used.
›Does this connect to my database?
No. It generates code and nothing else — there is no connection string field and no network request. Copy the output and run it wherever your database actually is.
›Should I use find or an aggregation pipeline?
find for filtering, projecting and sorting — it is simpler and the planner handles it well. Aggregate when you need to group, join with $lookup, reshape documents, or compute fields. An aggregation whose only stage is $match is a find with extra syntax.
›Why is $regex emitted as a string with $options rather than a literal?
Because a regex literal is not valid in Python or in JSON, and the same query should work across all three targets. The string form with an explicit $options is portable and behaves identically.
›Does the generated code handle errors?
No — it is the query, not an application. The Node output uses await so it participates in your existing error handling, and PyMongo raises on failure by default. Wrap them as your codebase does.
›What accumulators does the $group stage support?
$sum, $avg, $min, $max and $count — the ones that cover most reporting queries without an $expr fragment. $count is emitted as $sum: 1 rather than the Mongo 5+ $count accumulator, so the output also runs on older server versions. Anything more elaborate, like $push or a custom $expr, is exactly the case the raw value type exists for.
Last updated