Mongo Query Builder
Build MongoDB find filters and aggregation pipelines visually, and get them as mongosh, Node driver or PyMongo code — with OR runs always grouped explicitly.
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.
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.
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
›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