Type something to search...
Mastering mongosh: Essential MongoDB Shell Commands

Mastering mongosh: Essential MongoDB Shell Commands

Every MongoDB developer ends up in the shell sooner or later: to check what a document actually looks like, to fix a bad record in production, to see why a query is slow, or to run a one-off migration. GUIs are great for browsing, but when you need to do something precise and repeatable, typing is faster.

mongosh, the MongoDB Shell, replaced the old mongo shell a few years ago. It's built on Node.js, so it's a real JavaScript REPL with modern syntax, top-level await, syntax highlighting, smart autocomplete, and the ability to load npm packages. It also has a scriptable command-line interface, a configuration API, and a startup file for your own helpers. Most people use about a tenth of it.

This guide covers connecting, navigating databases and collections, running queries and updates efficiently, working with cursors and output, administration and diagnostics commands, scripting with --eval and files, and customizing the shell so it fits the way you work.

Connecting

With no arguments, mongosh connects to mongodb://127.0.0.1:27017:

mongosh

For anything else, pass a connection string:

# A specific database on a local server
mongosh "mongodb://localhost:27017/shop"

# An Atlas cluster; mongosh prompts for the password
mongosh "mongodb+srv://dev-cluster.ab1cd.mongodb.net/shop" --username app_dev

# A replica set with a read preference
mongosh "mongodb://db1:27017,db2:27017,db3:27017/shop?replicaSet=rs0&readPreference=secondaryPreferred"

Leaving the password out of the command is deliberate. mongosh prompts for it, which keeps it out of your shell history and out of the process list that other users on the machine can see.

Useful flags:

FlagWhat it does
--username, -uUsername (password is prompted if omitted)
--authenticationDatabaseDatabase that holds the user (usually admin)
--eval "code"Run code and exit
--file, -fRun a script file and exit
--quietSuppress the startup banner and warnings
--nodbStart without connecting (useful for testing JavaScript)
--norcSkip loading .mongoshrc.js
--jsonPrint --eval results as Extended JSON (canonical or relaxed)
--tlsEnable TLS for self-hosted servers

To leave, type exit, quit(), or press Ctrl+D.

Finding Your Way Around

These commands answer "where am I and what's here?":

show dbs                 // list databases with sizes
use shop                 // switch to (or lazily create) a database
db                       // print the current database name
show collections         // list collections in the current database
show users               // users defined on the current database
show roles               // roles on the current database
show profile             // recent entries from the database profiler

use doesn't create anything by itself. The database appears in show dbs only after something is written to it.

To work with another database without switching, use getSiblingDB:

const analytics = db.getSiblingDB("analytics");
analytics.events.countDocuments();

And to reach a collection whose name isn't a valid JavaScript identifier (it contains a hyphen, or starts with a number), use getCollection or bracket notation:

db.getCollection("order-items").findOne();
db["2026_imports"].countDocuments();

Querying and Updating Efficiently

All the standard collection methods work in mongosh exactly as in the drivers. A few habits make them faster to use interactively.

Keep Output Small

Project only the fields you want to see, and sort explicitly so results are stable:

db.orders
  .find({ status: "pending" }, { orderNo: 1, total: 1, createdAt: 1, _id: 0 })
  .sort({ createdAt: -1 })
  .limit(5);
[
  { orderNo: 'A-10432', total: 129.5, createdAt: ISODate('2026-09-26T07:41:12.004Z') },
  { orderNo: 'A-10431', total: 42, createdAt: ISODate('2026-09-26T07:12:55.310Z') },
  ...
]

Count Before You Change

Before any updateMany or deleteMany, run the same filter through countDocuments and check that the number makes sense:

const filter = { status: "pending", createdAt: { $lt: ISODate("2026-08-01") } };
db.orders.countDocuments(filter); // 318, as expected
db.orders.updateMany(filter, { $set: { status: "expired" } });

Storing the filter in a variable guarantees that the count and the update use exactly the same condition. It's a tiny habit that prevents very large mistakes. (For the full set of write methods, see MongoDB CRUD Operations Explained with Practical Examples.)

Use Real JavaScript

Because mongosh is a Node.js REPL, you can use variables, functions, loops, destructuring, and top-level await:

const since = new Date(Date.now() - 24 * 60 * 60 * 1000);

const [summary] = await db.orders
  .aggregate([
    { $match: { createdAt: { $gte: since } } },
    { $group: { _id: null, orders: { $sum: 1 }, revenue: { $sum: "$total" } } },
  ])
  .toArray();

print(`Last 24h: ${summary.orders} orders, $${summary.revenue.toFixed(2)}`);
Last 24h: 214 orders, $18342.75

(Collection methods in mongosh are synchronous-looking for convenience, so await is optional in interactive use. It's harmless, and it keeps snippets portable to driver code.)

Working with Cursors and Output

find() returns a cursor. In interactive mode, mongosh prints the first batch (20 documents by default) and tells you to type it for more:

db.events.find();
// ... 20 documents ...
// Type "it" for more
it;

Change the batch size for the current session with config.set:

config.set("displayBatchSize", 50);

For scripts and exports, convert a cursor to an array, or iterate it:

db.users.find({ plan: "pro" }).toArray().length;

db.users.find({ plan: "pro" }, { email: 1 }).forEach((u) => print(u.email));

Avoid toArray() on huge result sets; it loads everything into memory. Iterating with forEach or a for...of loop streams documents in batches instead.

Printing

  • print(value) writes plain text.
  • printjson(value) pretty-prints an object in the shell's format.
  • EJSON.stringify(value, null, 2) produces Extended JSON, which preserves types like dates and ObjectIds and is ideal for saving documents to a file or pasting into tests.
print(EJSON.stringify(db.users.findOne({ _id: 1 }), null, 2));
{
  "_id": 1,
  "name": "Ada Lovelace",
  "createdAt": { "$date": "2026-09-01T10:15:00Z" }
}

Multi-Line Editing

For long pipelines, type .editor to enter a multi-line editing mode, paste or type your code, and press Ctrl+D to run it. You can also set an external editor and use the edit command:

config.set("editor", "code --wait")   // or "vim", "nano"
edit                                  // opens a temporary file in your editor
edit pipeline                         // edit an existing variable

When you save and close the file, mongosh evaluates its contents.

Indexes and Query Analysis

db.orders.getIndexes();
db.orders.createIndex({ status: 1, createdAt: -1 });
db.orders.dropIndex("status_1_createdAt_-1");

// Hide an index to test whether anything depends on it (reversible)
db.orders.hideIndex("status_1_createdAt_-1");
db.orders.unhideIndex("status_1_createdAt_-1");

To see how a query executes, use explain:

db.orders
  .find({ status: "pending" })
  .sort({ createdAt: -1 })
  .explain("executionStats");

The key numbers are executionStats.nReturned, totalKeysExamined, and totalDocsExamined. If documents examined is far larger than documents returned, the query needs a better index. There's a complete walkthrough in Using Explain to Analyze and Debug Slow MongoDB Queries.

Administration and Diagnostics

These are the commands you reach for when something is wrong.

Sizes and Stats

db.stats({ scale: 1024 * 1024 }); // database stats in MB

// Collection storage details via the $collStats stage
db.orders.aggregate([
  { $collStats: { storageStats: { scale: 1024 * 1024 } } },
  {
    $project: {
      "storageStats.size": 1,
      "storageStats.storageSize": 1,
      "storageStats.totalIndexSize": 1,
      "storageStats.count": 1,
    },
  },
]);

The older db.collection.stats() helper still exists but is deprecated in favor of $collStats.

What's Running Right Now

// Operations running longer than 5 seconds
db.currentOp({ active: true, secs_running: { $gt: 5 } });

// Kill one by its opid
db.killOp(12345);

currentOp shows the operation, its namespace, the filter, how long it's been running, and whether it's waiting on a lock. Killing an operation is safe for reads; for writes, whatever was already applied stays applied.

Server Health

db.serverStatus().connections; // current and available connections
db.serverStatus().opcounters; // inserts, queries, updates since startup
db.hello(); // is this node primary? which replica set?
db.version(); // server version

Replica Sets and Sharding

rs.status(); // member states, health, replication lag
rs.conf(); // replica set configuration
rs.printSecondaryReplicationInfo();
sh.status(); // sharded cluster overview (run via mongos)

rs.status() is the first thing to check when an application reports "not primary" errors or stale reads. Look at each member's stateStr and optimeDate.

Users and Roles

use admin
db.createUser({
  user: "reporting",
  pwd: passwordPrompt(),
  roles: [{ role: "read", db: "shop" }]
})

db.getUsers()
db.grantRolesToUser("reporting", [{ role: "read", db: "analytics" }])
db.changeUserPassword("reporting", passwordPrompt())
db.dropUser("reporting")

passwordPrompt() asks for the password interactively instead of putting it in history.

Scripting with mongosh

One-Liners with --eval

--eval runs code and exits, which makes mongosh a handy building block in shell scripts:

mongosh "$MONGODB_URI" --quiet --eval 'db.orders.countDocuments({ status: "pending" })'
318

Add --json=relaxed to get machine-readable output you can pipe into jq:

mongosh "$MONGODB_URI" --quiet --json=relaxed \
  --eval 'db.orders.find({ status: "pending" }, { orderNo: 1, _id: 0 }).limit(3).toArray()' \
  | jq -r '.[].orderNo'

Script Files

For anything longer, write a .js file and run it with --file or pass it as a positional argument:

// expire-orders.js
const cutoff = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
const filter = { status: "pending", createdAt: { $lt: cutoff } };

const count = db.orders.countDocuments(filter);
print(`Expiring ${count} orders created before ${cutoff.toISOString()}`);

const result = db.orders.updateMany(filter, {
  $set: { status: "expired" },
  $currentDate: { updatedAt: true },
});
print(`Modified: ${result.modifiedCount}`);
mongosh "$MONGODB_URI" --quiet --file expire-orders.js

Inside an interactive session, load("expire-orders.js") runs the same file in the current context, and any functions it defines stay available afterward.

In scripts, remember that use shop is an interactive helper. It works in files run by mongosh, but db = db.getSiblingDB("shop") is more explicit and works everywhere.

Using npm Packages

Because mongosh runs on Node.js, require works for built-in modules and for packages installed in a local node_modules folder:

const fs = require("fs");
const docs = JSON.parse(fs.readFileSync("./seed/products.json", "utf8"));
db.products.insertMany(docs);

This is handy for small seeding and data-fix scripts. For large imports, mongoimport is faster.

Customizing the Shell

The config API

config holds persistent shell settings:

config; // show all settings
config.set("historyLength", 5000); // keep more history
config.set("inspectDepth", 10); // print more deeply nested objects
config.set("displayBatchSize", 50);

Settings persist across sessions. If nested documents appear as [Object] in output, raising inspectDepth fixes it.

.mongoshrc.js

On startup, mongosh runs ~/.mongoshrc.js if it exists. It's the place for your own helpers and a custom prompt:

// ~/.mongoshrc.js
prompt = () => {
  const hello = db.hello();
  const role = hello.isWritablePrimary
    ? "primary"
    : hello.secondary
      ? "secondary"
      : "standalone";
  return `${db.getName()}@${role}> `;
};

// Show the 5 newest documents in a collection
globalThis.latest = (coll, n = 5) =>
  db.getCollection(coll).find().sort({ _id: -1 }).limit(n).toArray();

// Quick field-type audit on a collection
globalThis.typesOf = (coll, field) =>
  db
    .getCollection(coll)
    .aggregate([
      { $group: { _id: { $type: `$${field}` }, count: { $sum: 1 } } },
    ])
    .toArray();

Now latest("orders") and typesOf("orders", "total") are available in every session. A prompt that shows the database and the node's role is a cheap safety net: it's hard to forget you're on a production primary when the prompt says so.

Snippets

mongosh also supports snippets, community and official packages of helpers you can install from inside the shell:

snippet search
snippet install analyze-schema

Check what a snippet does before installing it, just as you would any npm package.

Telemetry

mongosh collects anonymous usage telemetry by default. To turn it off, run disableTelemetry(), which persists across sessions.

Common Mistakes

Typing mongo instead of mongosh. The legacy shell is gone from current releases. Most old snippets work unchanged in mongosh, but deprecated helpers like cursor .count() should be replaced with countDocuments().

Putting passwords on the command line. They end up in shell history and process listings. Let mongosh prompt, or use passwordPrompt() in scripts that create users.

Running bulk writes without counting first. Store the filter in a variable, count, then update or delete with the same variable.

Forgetting which database you're in. db tells you, and a custom prompt makes it permanent. Many "the data disappeared" scares are really "I was in the test database."

Calling toArray() on millions of documents. It buffers everything in memory and can crash the shell. Iterate the cursor or use $out/$merge for large transformations.

Treating mongosh scripts as throwaway. Scripts that fix production data are code. Keep them in version control with a comment explaining why they were run.

Conclusion

mongosh is much more than a place to type find(). You can connect to any deployment with a connection string, move between databases with use and getSiblingDB, query with real JavaScript and top-level await, inspect performance with explain and currentOp, check replica set health with rs.status(), and automate everything with --eval and script files. The config API and .mongoshrc.js turn it into a tool shaped around your own habits.

For a next step, create a ~/.mongoshrc.js with a prompt that shows your current database and node role, plus one helper you'll actually use, like typesOf. Five minutes of setup pays off every time you open the shell.

Tags :
Share :

Related Posts

A Complete Guide to MongoDB Query Operators

A Complete Guide to MongoDB Query Operators

Your first MongoDB queries are usually simple equality filters: find the user with this email, find orders with this status. That covers a surprising

Continue Reading
Async MongoDB in Python with Motor and FastAPI

Async MongoDB in Python with Motor and FastAPI

FastAPI runs your endpoints on an event loop. That's what lets a single worker juggle hundreds of concurrent requests: while one request waits on the

Continue Reading
Atlas Online Archive: Tiering Cold Data to Cut Costs

Atlas Online Archive: Tiering Cold Data to Cut Costs

Look at almost any production database and you'll find the same shape. A small slice of recent data gets nearly all the reads and writes: this week's

Continue Reading