Skip to main content
The query tab is a JavaScript shell. Write 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. There is no minimum server version; the driver adapts what it asks for to what the server answers.
MongoDB connection form with the multi-host Hosts editorMongoDB connection form with the multi-host Hosts editor

MongoDB connection form

On MongoDB 4.0 and later the database list is requested as authorized databases only, so an account without the listDatabases privilege still sees what it can read. On an older server that list comes back empty: name a Database on the connection instead.

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:
Over an SSH tunnel only the first host is used and the rest of the list is dropped. Replica set discovery and failover are off for that session, with nothing on screen to say so.

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; drop one 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 the db.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 stores null 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 as UUID("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.
SQL Preview tab of Create Table showing db.createCollection for articles with a $jsonSchema listing _id, title, tags, date and schemaVersionSQL Preview tab of Create Table showing db.createCollection for articles with a $jsonSchema listing _id, title, tags, date and schemaVersion

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.
Insert Document sheet over the empty events collection, holding a document with a name, a channel, a date, a count and a list of tagsInsert Document sheet over the empty events collection, holding a document with a name, a channel, a date, a count and a list of tags

The first document of an empty collection

The text is Extended JSON: quote every field name, and write an ObjectId as {"$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.

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.

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.

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 and returnDocument reach the server. A write returns the object mongosh returns: matchedCount, modifiedCount, upsertedCount and upsertedId for an update, deletedCount for a delete, insertedId for an insert.

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 _id cannot be updated or deleted. The save is refused rather than matched on the remaining fields, and every change stays pending. Keep _id in 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.
  • _id is read-only in the grid and the row inspector, and a row added with Add Row is inserted without one so the server generates it. To choose your own, use Insert Document….
  • 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.
  • 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 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.
  • 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 updateMany filtered 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.

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.