db.orders.find({status: "new"}) the way mongosh takes it, unquoted keys and all, and keep going into variables, loops and forEach. Collections appear as tables in the sidebar, with top-level fields as columns and nested objects as formatted JSON.
The MongoDB driver is not in the app. Picking MongoDB in the Choose a Database sheet offers the
download before the form opens, and opening a saved MongoDB connection installs it without asking.
Settings > Plugins > Browse > MongoDB Driver installs it up front. See Plugins.
Quick setup
1
Create Connection
Click New Connection…, select MongoDB, and enter hosts and credentials
2
Test Connection
Click Test Connection to verify, then Save & Connect
Connection settings
Naming a Database skips listing every database on the server, which is worth doing on a cluster with hundreds. Leave it empty and the first non-system database opens instead.
Cmd+K switches either way, on the same connection, with no reconnect.
Auth Database is a separate question: it says where your account is defined, not what you browse. Left empty, it follows the Database field. An account defined in admin needs Auth Database set to admin whenever Database names something else, or authentication fails. SRV connections authenticate against admin regardless, unless told otherwise. Switching databases in the app never changes it, so browsing a database your user has no account in is fine.
Also in Advanced: Read Preference, Write Concern, Use SRV Record, Replica Set name, and Legacy UUID Encoding. The server has to be MongoDB 4.0 or later; the driver refuses to connect to anything older.
Write Concern covers every insert, update and delete, from a query tab or from a save in the data grid, unless the statement passes its own writeConcern. Index, collection and database commands, and db.runCommand, go to the server as written. On a server that is not a replica set, pick Default, Majority or 1: with 2 or 3 every write fails with cannot use 'w' > 1 when a host is not replicated. journal and wtimeoutMS in a pasted connection URL go out with every write too.


MongoDB connection form
listDatabases privilege still sees what it can read.
Connection URL
mongodb+srv:// resolves hosts through DNS SRV records and takes no port; one pasted into the field is stripped before connecting. Plain mongodb:// keeps whatever port you give it. See Connection URL Reference.
MongoDB Atlas (SRV)
An Atlas connection needs the cluster hostname, a username, and a password. Atlas requires SRV and TLS, so a host ending in.mongodb.net gets both turned on for you, TLS only if the SSL mode was still Disabled. Elsewhere the Use SRV Record toggle in Advanced does the same job. Add your current IP to the cluster’s access list in the Atlas console first: traffic from an address that is not on it times out rather than failing.
Replica sets
The Hosts field takes comma-separated pairs (host1:27017,host2:27017,host3:27017), the primary is discovered, and writes are routed to it. Set the replica set name in Advanced. A multi-host URI pastes in directly:
Browsing collections
Click a collection to page through its documents. The columns are the fields in the documents on screen, then any field the collection’s$jsonSchema validator declares that none of them hold, so an empty collection still shows the fields it was created with. A top-level ObjectId renders as its hex string. A nested object or array shows as Extended JSON in the order it is stored: {"$oid": "…"}, {"$date": "2024-05-01T10:00:00.123Z"}, {"$numberLong": "5"}, and 3.0 for a double that holds a whole number.
An edited cell keeps its field’s type. A date stays a date, whether typed as 2024-01-02T03:04:05Z, as 2024-01-02, or picked in the date picker, which writes your local time. An ObjectId stays an ObjectId, an integer past 2^53 is sent as a 64-bit integer, and a field the validator declares as string is written as text even when it reads 123 or true. Where the grid has no type to go on, 123 is a number and {"a": 1} a document; text that only looks like JSON is written as a string.
Editing a nested object or array writes only the paths that changed, such as address.city or tags.1, so every other value in it keeps its type and its place. Adding two keys at once, reordering keys, or changing an array’s length writes the whole value instead. A binary cell keeps its BSON subtype when its bytes are edited, and a duplicated or pasted row keeps its binary fields.
A field whose own name contains a dot, starts with $ or is __proto__ is written with $setField, which needs MongoDB 5.0 or later. On an older server the save is refused and nothing is sent.
The filter bar’s column picker lists paths inside nested objects and arrays of objects, so customer.country and items.sku filter directly; a row on an array field chooses any element or same element, which makes one array entry satisfy every row set to it. See Filtering. A field name containing a literal dot is left out of the picker, since MongoDB reads a dot as a path separator; reach it with $getField inside $expr.
The Structure tab lists a collection’s indexes, and renames or removes a field across the whole collection. Drop an index from a query tab with db.users.dropIndex("email_1"). New Database asks for a database name and a first collection, both required. New View opens a query tab holding a db.createView("view_name", "source_collection", [pipeline]) template, and editing a view pre-fills db.runCommand({"collMod": …}).
Views
Views sit in their own Views group in the sidebar and open read-only, with no cell edits, Add Row or Delete, and no Rename or Truncate on the context menu. Show DDL writes thedb.createView(…) that makes the view again, pipeline and collation included. Edit View Definition opens a collMod holding the view’s source and pipeline: run it to change the view in place, collation kept. Both write each value in its own BSON type, so the text runs the same in a query tab and in mongosh: NumberLong("…") for an Int64, Double(1.0) for a whole Double, ISODate("…") for a date, BSONRegExp("…", "…") for a regular expression. A date before year 1 or after 9999 is written as new Date(…). Export writes a view’s documents; import and table transfer skip views.
A time-series collection is listed with the other collections, and its DDL starts with a // Time series: line naming its time and meta fields. system.views, system.profile and the system.buckets.* collections behind time series are marked as system collections, with no Rename or Truncate.
Indexes
The Structure tab’s Indexes list shows each index’s fields in key order, and its type from the key:
Show DDL writes one
createIndex per index with every option the server reports: TTL, partial filter, collation, text weights, wildcard projection and 2d bounds. Values are written the way a view’s are, so run that text in a query tab and the indexes it builds match the originals, value types included. The collation leaves out the server’s ICU version, so the statement also runs on a server built with a different ICU.
Missing fields and null
A field a document does not have reads No Field; a field holding null reads NULL. Set Value > NULL storesnull and keeps the field. To delete a field, right-click the cell and choose Remove Field, or choose it from the value menu of the field in the inspector; saving sends $unset. A validator that lists the field in required refuses the save with Document failed validation.
A new row starts with every field missing, and only the cells you fill in are written, so a row saved untouched inserts a document holding only a generated _id. Duplicate and Paste keep which fields were missing and which held null. Pasted text carries no such distinction, so a NULL in it leaves the field out. Undo, discarding, and restoring a save put a removed field back and take out a field that was filled in. A restore treats a field removed or added since the save as Changed since the save, and leaves that document alone.
Set Value > NULL appears only where the validator takes null: the field’s type lists null or is not declared, and any enum on it lists null too. A rule over the whole document, such as a top-level anyOf or a query operator beside $jsonSchema, hides it on every field. A validator set to validationAction: "warn" hides it on none. The filter bar’s is NULL matches a field holding null and a missing field alike, as {field: null} does in mongosh. To find only documents that lack a field, run db.users.find({nickname: {$exists: false}}) in a query tab.
Binary UUIDs
A binary subtype 4 field renders asUUID("8cd003eb-4a25-4324-9332-88fce2da0d1a"). Subtype 3 is the legacy format, and its bytes do not say which driver wrote them, so it stays BinData(3, "…") until Legacy UUID Encoding on the connection is set to Java, C#, or Python. Match it to the driver that wrote the data: the wrong choice shows a valid-looking but wrong UUID.
Once set, the value renders as LegacyJavaUUID("…") and reads that way everywhere, filters and MQL export included. Nothing stored is rewritten, uuidRepresentation=javaLegacy in a pasted URL sets the same option, and a change takes effect on the next connect.
Creating a collection
Choose Database > New Table…. Each row of the grid is a field, and its type is one of the BSON types in the list:objectId, string, int, long, double, decimal, date, bool, array, object and the rest. Create Table turns the rows into a $jsonSchema validator and runs db.createCollection("articles", {"validator": …}), which SQL Preview shows first.


Nullable fields also accept null; NOT NULL ones go in required
MongoDB enforces the validator with its defaults,
strict and error, so an insert or edit of the wrong type fails with Document failed validation. Primary Key on any field other than _id is refused, and so are field names that start with $ or contain a dot.
Each row on the Indexes tab runs as a createIndex after the collection is made, with its fields in the order listed. BTREE is an ascending key, HASH is hashed, FULLTEXT is text, and SPATIAL is 2dsphere. A hashed index takes one field and cannot be unique, and a text index cannot be unique either.
Inserting documents
A field exists only in the documents that hold it, so a collection with no documents and no validator shows_id alone and has no column to type a field into. Choose Edit > Insert Document… to write a whole document instead. It is on a row’s context menu too, and on the context menu of an empty grid.


The first document of an empty collection
{"$oid": "…"}, a date as {"$date": "2024-05-01T10:00:00Z"} and a decimal as {"$numberDecimal": "1.10"}. A whole number is stored as a 32-bit integer, or as a 64-bit one when it does not fit; {"$numberLong": "5"} stores a small 64-bit integer. A number with a decimal point is a double. Fields are stored in the order written. Leave out _id and the server generates one.
Every field name is stored as typed, an empty name, a dotted name such as a.b and a top-level name starting with $ included. MongoDB before 5.0 refuses the last two kinds. mongosh reads a document holding both $ref and $id at the top level as a DBRef, so keep that pair for references.
Editing documents
A cell edits one value. To add, rename or remove a field, select one row and choose Edit > Edit Document…, or choose Edit Document… on the row’s context menu. It works on a collection tab with no unsaved grid edits, not on a query tab. The sheet reads the document from the server again, so it shows what is stored now rather than what the grid loaded. A value reads as plain JSON when that stores the same type, and keeps its wrapper when it would not: a 64-bit integer small enough for 32 bits stays{"$numberLong": "5"}, and a date before 1970 or after 9999 stays {"$date": {"$numberLong": "…"}}.
Save replaces the whole document with the text, in the order written. A field left out of the text is removed. A dotted name, or a name starting with $ inside a subdocument, is stored as that exact name rather than read as a path. _id cannot change, and leaving it out keeps it.
If anyone changed or deleted the document after the sheet opened, nothing is saved and the sheet says so. Copy your text, close the sheet and open the document again.
Renaming and removing fields
In the Structure tab, edit a field’s Name to rename it, or remove its row to remove the field, then choose Save Changes. Each change is oneupdateMany over the collection, and Preview SQL shows it:
$jsonSchema validator declares the field in properties, required or dependencies, the save starts with a db.runCommand({"collMod": …}) that renames or removes the field there as well, so a collection made with New Table… keeps validating. A field only the validator declares, with no document holding it, is removed from the validator.
With Write Concern set on the connection, or w, journal and wtimeoutMS in its connection string, every statement carries it as {"writeConcern": {"w": "majority"}}, and Preview SQL shows it. w=0 without journal=true goes out as w: 1.
The save is refused before anything is written when:
The checks for a document holding both names and for a document the validator would reject run on Save Changes only, never in Preview SQL. Under a
moderate validator, a document the validator already rejected before the save is left alone. Each reads every document that holds a changed field and stops at the query timeout in Settings > General, or after 10 minutes when the timeout is No limit. A check that times out changes nothing. Every check reads the primary, whatever the connection’s Read Preference. Once they pass, the save reads the collection’s options, indexes and views again, and stops if the options changed or an index, search index or view now uses the field.
An updateMany that stops partway, at the query timeout or on a duplicate key, keeps the documents it already changed. Another client can also write the old name while the save runs, so after the last statement the save counts the documents that still hold it, and reports how many when any do. It also reads the indexes, search indexes and views again, and reports one another client made on either name while the save ran. A collMod whose write concern was not met stops the save with the server’s reason, after the validator was already changed. Either way the change stays queued in the Structure tab: choose Save Changes again and it finishes the rest, because each statement skips the documents already done. A document left holding both names stops that second save instead, with the message for that case. Removing a field asks for confirmation, as a dropped column does.
When the save rewrote the validator, it then looks for a document holding a changed name that the new validator rejects. One another client wrote with only the new name, after the checks and before the collMod, passed the old validator and fails the new one, and its next update fails with Document failed validation. The save reports its _id: fix that document, then choose Save Changes again.
Writing queries
Queries run through JavaScriptCore, so a statement is JavaScript and the whole language is available: object literals with unquoted keys, single-quoted strings, regex literals such as/abc/i, new Date(), arithmetic, // and /* */ comments.
Date("2020-01-01") without new gives a date rather than the string plain JavaScript would
return, so a filter written that way keeps matching. new Date and instanceof Date are the
native ones.
$type, $regex and $options objects reach the server as the operator documents they are
written as, the same as in mongosh: {tags: {$type: ["array", "null"]}} and
{name: {$regex: "^a", $options: "i"}} filter in a query tab and in the filter bar’s
Raw Filter row. Such an object is a document everywhere, so insertOne stores
{$regex: "a", $options: "i"} as an embedded document and MongoDB refuses it inside $in. Write
a regular expression value as a literal, /^a/i.
Scripts
The shell is per connection, so a variable or function defined in one statement is there for the next, in any tab on that connection, until you disconnect.print and printjson write to the result grid, one row per line, whenever the statement itself
returns no documents. A statement that returns documents shows those instead, with the printed
lines on the status line under the grid.
Values
A number written without a constructor is stored by what it holds:
mongosh stores every whole number outside int32 as a double; write
Double(3000000000) for the
same result here. Past 2^53 a JavaScript number cannot hold every integer, so 9007199254740993
is already 9007199254740992 when the statement runs. Pass an exact 64-bit integer as a string:
Long("9007199254740993"). Because -0 is a double, {$inc: {n: Math.round(-0.2)}} turns an
int32 field into a double, the same as in mongosh.
A value out of range throws when the constructor runs.
Int32(2147483648) and
NumberLong("9223372036854775808") throw where mongosh wraps them round to a negative number.
A document read into a script holds these:
Written back, a whole double is stored by the rules for a bare number:
5.0 becomes int32, as in
mongosh, and 3000000000.0 becomes int64 where mongosh keeps a double. Wrap it in Double() to
keep it a double. A regular expression goes back exactly as the server sent it, options included.
Collection references
db.users, db["users"], or db.getCollection("users"). Use getCollection for names with dots
or spaces, names starting with a digit, and names that collide with a database method: db.stats
and db["stats"] both reach the method, because the shell cannot tell which you meant.
Cursors
find() and aggregate() return a cursor and touch nothing until something reads it. Chain
sort, skip, limit, projection, hint, collation, maxTimeMS, batchSize and
allowDiskUse onto it, then read it with forEach, map, toArray, hasNext/next, itcount,
count or explain. On an aggregation, sort, skip and limit become $sort, $skip and
$limit stages appended to the pipeline.
A modifier after the cursor has started throws, the same as mongosh. Split it into two statements,
or set the modifier before the first read.
Write options
updateOne, updateMany, replaceOne, findOneAndUpdate and the delete calls take an options
document, and upsert, arrayFilters, hint, collation, returnDocument and writeConcern
reach the server. insertOne, insertMany, insert and bulkWrite take writeConcern too, and
insertMany takes ordered: false to go on inserting past a document that fails.
remove(filter, true) and remove(filter, {justOne: true}) delete one matching document. A write
returns the object mongosh returns: matchedCount, modifiedCount, upsertedCount and
upsertedId for an update, deletedCount for a delete, insertedId for an insert.
writeConcern takes mongosh’s journal and wtimeoutMS as well as j and wtimeout. Naming any
of w, j or wtimeout replaces the connection’s Write Concern for that write; an empty
writeConcern: {} keeps it. With w: 0 and no j: true the server does not report what a write
did, so the result is {acknowledged: false} with no counts. findOneAndUpdate and the other
find-and-modify calls still return the document.
When a write stops part-way
MongoDB keeps whatever a write changed before it failed, timed out or was killed, and undoes nothing. AnupdateMany that fails on its third document has already changed the first two. The
error names this under the server’s message: insertMany and bulkWrite give the count, and
updateMany and deleteMany say only that some documents may have changed, as does any write sent
with w: 0. A statement that ran several writes counts them all, and so does an export whose query
fails after the writes before it. Check the data before you run the statement again.
Methods
Collection:find, findOne, aggregate, countDocuments/count, estimatedDocumentCount,
distinct, insertOne/insertMany/insert, updateOne/updateMany/update, replaceOne,
save, deleteOne/deleteMany/remove, findOneAndUpdate/findOneAndReplace/findOneAndDelete,
bulkWrite, createIndex/createIndexes, dropIndex/dropIndexes, getIndexes,
hideIndex/unhideIndex, drop, renameCollection, stats, dataSize, storageSize,
totalIndexSize, totalSize, isCapped, validate, explain.
Database: getCollection, getSiblingDB, getCollectionNames, getCollectionInfos,
createCollection, createView, dropDatabase, stats, version, serverStatus, hostInfo,
currentOp, killOp, runCommand, adminCommand. use <name>, show dbs and show collections work as
typed. Anything with no method of its own goes through db.runCommand({…}).
Cmd+Shift+F reformats by nesting depth. Autocomplete offers collections, collection methods,
cursor methods after find(), nested field paths such as address.city, and the $ operators
valid at the cursor; see Autocomplete. For a query plan, chain
.explain("executionStats") onto the cursor.
SSL/TLS
New connections default to Disabled, and the driver has no TLS fallback: Preferred behaves exactly as Required, which is what the SSL pane warns about. For an unencrypted local instance use Disabled or SSH tunneling. See SSL/TLS.Limitations
- A row with no
_idcannot be updated or deleted. The save is refused rather than matched on the remaining fields, and every change stays pending. Keep_idin the projection so every row carries one. - A binary value keeps the subtype it was read with. Bytes typed into a new row are saved as subtype 0 where the validator declares the field
binData. - Other bytes refuse the save when their subtype was never read, or was read as two different ones: bytes typed into an existing document, pasted from another collection, or put back by Data Rewind after TablePro restarts. Write that value with a query.
- A field holding text in some documents and objects or arrays in others takes no value that reads as JSON from the grid, since it could be either type: the save is refused. Write it with a query.
- A new row, duplicate or paste cannot hold a field named
__proto__, or an empty field name at any depth: the save is refused. Use Remove Field on that cell, save, then set it on the saved document. _idis read-only in the grid, the row inspector and Edit Document…, and a row added with Add Row is inserted without one so the server generates it. To choose your own, use Insert Document….- Edit Document… opens a message instead of the text for a document it cannot write back exactly: a top-level field starting with
$, a repeated field name, a subdocument shaped like{"$numberInt": "5"}, more than 28 levels of nesting, or a guarded save over 16 MB, which a document made mostly of numbers reaches at about 5 MB. Change such a document withupdateOnein a query tab. - The server replaces a top-level
Timestamp(0, 0)with the current time whenever a whole document is written, so Insert Document… stores the time there and Edit Document… refuses a document holding one.$setin a query tab keeps it. - Edit Document… also refuses a document holding a NaN, double or decimal, or JavaScript code with a scope. Change such a document with
updateOnein a query tab. - Edit Document… needs MongoDB 4.0 or later, and refuses a view and a time-series collection. Edit the collection a view reads, and change time-series documents with
updateManyin a query tab. - In a collection whose default collation is not simple, saving a document with a string
_idreads the whole collection to find it. On a large collection, change the document withupdateOnein a query tab instead. - Transactions are not exposed. Statements always run standalone, on any topology.
- A collection takes one text index. A second FULLTEXT row fails after the collection and the indexes before it are created: list every text field in one index instead.
- New Table… writes the validator with the server’s own level and action. To log bad documents instead of refusing them, run
db.runCommand({collMod: "articles", validationAction: "warn"})after creating the collection. - Nested paths filter but do not sort. Sorting works on the grid’s own columns.
- Filtering, sorting or editing a field named
""or starting with$fails in the grid. A dotted name such asa.breads as the path tobinsidea. Use$getFieldand$setFieldin the query editor. - same element covers a field one array deep. A path through an array inside another array needs nested
$elemMatch, so those filter with dot notation only. - GridFS buckets are not browsable, and change streams are unsupported.
- A
Codescope holding an object that opens with$type,$regexor$options, such asCode("f", {a: {$type: "binData"}}), fails withThis is not a document MongoDB can read. List another key of that object first. - A script that loops without touching the database cannot be stopped: JavaScriptCore has no public way to interrupt one.
Cmd+.stops anything that reads, writes or prints, which covers every query. A script silent for 120 seconds is abandoned and the shell restarts. - Field names that are whole numbers up to 4294967294 (
"0","12") sort ahead of the rest in a document literal, which is what JavaScript does with them. A nested object with such a key after another key, or with a key named__proto__, refuses the save when it is duplicated or written whole. Use Insert Document… or a query for it. Decimal128("…")with more than 34 significant digits, or an exponent outside the decimal128 range, fails when the statement runs with “This is not a document MongoDB can read”. Round a long value to 34 digits. For an exponent out of range, store the value scaled to a unit that fits, or as a double or a string.- An MQL export of a view holds the view’s documents, not the view, so restoring the file creates a collection of that name. Drop that collection and run the view’s Show DDL text to get the view back.
- A time-series collection takes inserts and deletes from the grid, but refuses an edited cell and a rename, and the server’s error is shown. Change its documents from a query tab with
updateManyfiltered on the meta field. - A filter or validator with a regular expression under
$regex, such as{email: {$regex: /@/i}}, is refused as a document MongoDB cannot read. Write{email: /@/i}or{email: {$regex: "@", $options: "i"}}instead. Show DDL writes such a validator the way the server holds it, which runs in mongosh but not in a query tab. - A DBPointer, an
undefined, or a date more than 100 million days from 1 January 1970 has no mongosh spelling, so Show DDL keeps its Extended JSON wrapper. A query tab runs that text; mongosh does not. bulkWriteruns its operations in order and stops at the first one that fails, even withordered: false. To go on past a failed insert, useinsertManywithordered: false.- A field rename checks indexes, search indexes, validators and views in the same database only. Other databases and application code keep the old name: update them after the rename.
- A field rename does not lock the collection. A validator another client sets in the milliseconds between the last check and the save’s
collModis replaced by it. - MongoDB before 4.4 cannot measure a document, so a rename to a longer name is not checked against the 16 MB limit there. Rename to a name no longer than the old one on those servers when documents are close to the limit.
- Compare & Sync compares MongoDB structures but writes no script for them, because a collection’s fields come from a sample of its documents.
Troubleshooting
Connection refused: check MongoDB is running (brew services start mongodb-community) and that the port and bindIp in mongod.conf match what you entered.
Authentication fails on connect: the error names the database that was authenticated against. If your user does not live there, set Auth Database in Advanced; otherwise check the username, password, and auth mechanism. The MQL editor does not parse db.getUsers() or db.createUser(); read users with db.runCommand({"usersInfo": 1}).
Timeout: for Atlas, add your IP to the cluster’s access list first. Otherwise verify host and port and check the network and firewall.
A collection is slow to open: a sort or filter on an unindexed field makes MongoDB read every document, even for 20 rows. Check the Structure tab for an index on that field. Cmd+. stops the query on the server.
The row total shows ~: that is the instant estimate from collection metadata. The automatic count is capped at 5 seconds and keeps the estimate if the server is slower; Count Exactly runs a real count against your query timeout. Views and time-series collections have no metadata count, so their estimate can be missing altogether.
