MongoDB Module
Read and write your own MongoDB database from a workflow: MongoDB Atlas, Azure Cosmos DB for MongoDB, or a server you run. Three tasks cover what a flow needs: Find documents, Save documents and Delete documents, with the flow's variables bound into the queries.
Typical uses: look a customer up by the email a chat or form gave you, record every order or conversation as a document, keep a counter or a status up to date with $inc and $set, feed a list of documents to an AI task or an email, or report on a collection with an aggregation pipeline.
Requirements
- A MongoDB cluster the engine can reach, and a database user with the roles the flows need (
readWriteon one database is enough for most flows;readfor report-only flows). - On Atlas, the engine's outbound IP must be in the project's IP access list.
Configuration
| Setting | Required | Notes |
|---|---|---|
| Connection string | yes | mongodb:// or mongodb+srv://, with the user and password. Atlas: Database → Connect → Drivers. Stored securely; never shown in logs or errors. |
| Database | yes | The database the tasks use. A task can name another one in its advanced options. |
| Default timeout (seconds) | no | 1–300. Empty: 30. A task can set its own. |
Writing queries
Filters, documents, updates and pipelines are the JSON you would type in the MongoDB shell (mongosh): quoted or unquoted keys, ObjectId("…"), ISODate("…"), {"$gte": 10}, and so on.
Variables are bound by type, not pasted as text. Write a flow variable without quotes and the task binds it the way a database driver binds a parameter:
| You write | With | The server receives |
|---|---|---|
{"email": {email}} | email = ann@example.com | {"email": "ann@example.com"} |
{"total": {"$gte": {minTotal}}} | minTotal = 100 (a number) | {"total": {"$gte": 100}} |
{"status": {"$in": {statuses}}} | statuses = a list | {"status": {"$in": ["open", "paid"]}} |
{record} | record = an object | the object as the document |
{"createdAt": {"$lt": {execution.startedAt}}} | a date | a BSON date |
A text value is always quoted and escaped, so a visitor's input can never add keys or operators to your query. A variable that does not exist binds as null.
Inside a string a variable is just text: {"_id": ObjectId("{customerId}")}, {"note": "Order {orderNumber} from {name}"}, ISODate("{since}").
Numbers typed in a form arrive as text. To compare them as numbers, compute a numeric variable first (a JavaScript task, a Date task) or compare on the text field the collection stores.
Results come back as plain variables: a list of documents ({orders.0.customer.name}, {orders.0._id}), one document ({customer.email}), a number or a list of values. Ids are text: an ObjectId is its 24-character hex, ready for ObjectId("{id}") in a later filter. Dates are dates, numbers are numbers, sub-documents and arrays keep their shape.
Find documents
One task with five operations. The result is written to the Output variable.
| Operation | Returns | Fields |
|---|---|---|
| Find documents | a list of documents | Filter, Maximum documents (1–5000, empty: 100), Sort, Projection, Skip |
| Find one document | the first match, or nothing | Filter, Sort, Projection, optional Found variable (true / false) |
| Count documents | a number | Filter |
| Distinct values | a list of values | Field (dotted paths work: address.city), Filter; at most 5000 values |
| Aggregate (pipeline) | a list of documents | Pipeline (a JSON array of stages), Maximum documents (added as a final $limit, except after $out / $merge) |
| Property | Notes |
|---|---|
| Collection | The collection name; a {variable} works. |
| Filter | MongoDB query document. Empty or {}: every document. |
| Sort | {"createdAt": -1, "name": 1}: 1 ascending, -1 descending. Advanced. |
| Projection | {"name": 1, "email": 1} keeps those fields (and _id); {"notes": 0} drops one. Advanced. |
| Skip | Documents to skip, for paging. Advanced. |
| Database, Timeout | Per-task overrides of the module configuration. Advanced. |
Example, the open orders of the customer who wrote in:
Operation: Find documents
Collection: orders
Filter: {"customerEmail": {email}, "status": "open"}
Sort: {"createdAt": -1}
Output: {orders}
Then {orders.0.total} is the latest order's total, and a Switch on {orders} being empty routes customers with no open order.
Save documents
| Operation | Fields | Result outcome |
|---|---|---|
| Insert one document | Document | inserted; {result.insertedId} is the new _id as text |
| Insert many documents | Documents (a JSON array or a {list} variable, up to 5000) | inserted; {result.insertedIds} |
| Update one document | Filter, Update, Upsert | updated, unchanged, not_found or upserted |
| Update many documents | Filter, Update, Upsert | same, with {result.matchedCount} and {result.modifiedCount} |
| Replace one document | Filter, Document, Upsert | same |
Update takes update operators only: {"$set": {"status": "closed"}, "$inc": {"visits": 1}, "$push": {"log": {entry}}, "$currentDate": {"updatedAt": true}}. A plain key at the top level is refused; to swap the whole document use Replace one document.
Upsert inserts when the filter matches nothing: for an update, the document is built from the filter's equalities and the update's values.
The optional Result variable receives { insertedId, insertedIds, insertedCount, matchedCount, modifiedCount, outcome }.
Example, one document per customer, created on first contact and updated after:
Operation: Update one document
Collection: customers
Filter: {"email": {email}}
Update: {"$set": {"name": {name}, "lastContact": {execution.startedAt}}, "$inc": {"contacts": 1}}
Upsert: on
Result: {saved}
{saved.outcome} is upserted the first time and updated after.
Delete documents
| Operation | Notes |
|---|---|
| Delete one document | The first document the filter matches. |
| Delete many documents | Every document the filter matches. |
Filter is required and must not be empty: a blank variable can never wipe a collection by accident. To empty a collection on purpose, use Delete many documents and turn on Allow an empty filter in the advanced options.
The optional Result variable receives { deletedCount }.
Errors
Every task takes its error flow on a failure and writes {mongoError} with code, message (a short sentence safe to show an end user) and detail (the server's own text, for the author; also in the step's log). Route on {mongoError.code} with a Switch:
| Code | When |
|---|---|
duplicate_key | A write hit a unique index (_id or another unique key): the conflict to route as a business case. |
invalid_input | A task field is missing or out of range (an empty collection, a limit above 5000, an update without operators, an empty delete filter). |
invalid_json | A filter, document, update or pipeline is not valid JSON once the variables are bound. |
configuration | No connection string or database, or a malformed connection string. |
connection | The server could not be reached (host, port, DNS, TLS, IP access list). |
authentication | The server refused the user or password. |
unauthorized | The user may not run that operation on that database or collection. |
timeout | The operation took longer than the timeout. |
write_error | The server rejected a write for another reason (a schema validation rule, say). |
command_error | The server rejected the query (a bad pipeline stage, an unknown operator): detail has the server's message. |
too_many | More than 5000 distinct values, or more than 5000 documents to insert at once. |
Connection strings never appear in messages: a driver text that echoes one has its credentials replaced by ***.
Limits
- At most 5000 documents per Find or Aggregate (set Maximum documents; page with Skip), 5000 distinct values, 5000 documents per Insert many.
- One operation per task; there are no multi-document transactions. Model a transaction as an upsert with a unique key, or keep the state in one document and change it with operators.
- The tasks use the
BsonDocumentshape: field names are kept exactly as stored, including_id.