Connecting
The default port is 27017. Build a URI for your host with the MongoDB connection string generator.
| Command | What it does |
|---|---|
| Connect to localhost:27017, database | |
| Connect to a host and open the database | |
| Host and port as flags instead of a URI | |
| Log in as , prompts for the password | |
| Same with flags; with no value prompts | |
| Atlas or any SRV record, TLS on by default | |
| Replica set, sends writes to the primary | |
| TLS with your own CA certificate | |
| Run one command and exit | |
| Start the shell without connecting | |
| Show the current connection string | |
| Log in from inside an open session | |
| Switch to another server or database in a script | |
| Leave the shell ( works too) | |
| Installed mongosh version |
Example: connect to a three-node replica set as an app user and confirm which node is primary.
mongosh "mongodb://[email protected],db2.internal,db3.internal/shop?replicaSet=rs0&authSource=admin" \
--quiet --eval 'db.hello().primary'
# Enter password: ********
# db1.internal:27017
Databases and Collections
A database or collection exists only after the first write, so on a new name creates nothing yet.
| Command | What it does |
|---|---|
| List databases with their size on disk | |
| Database names only, also works from scripts | |
| Switch to , created on first write | |
| Print the current database name | |
| List collections and views in the current database | |
| Collection names as an array, for scripts | |
| Query another database without switching | |
| Create a collection explicitly | |
| Create a time series collection | |
| Rename a collection | |
| Data size, storage size and object count for the database | |
| Fast document count from collection metadata | |
| DestructiveDelete the collection, its documents and indexes | |
| DestructiveDelete the current database |
Example: add a schema validator to so every document needs an email string.
use shop
db.runCommand({
collMod: "users",
validator: { $jsonSchema: { required: ["email"], properties: { email: { bsonType: "string" } } } },
validationAction: "error"
})
db.users.insertOne({ name: "No Email" }) // MongoServerError: Document failed validation
Insert
| Command | What it does |
|---|---|
| Insert one document; is generated if you leave it out | |
| Insert several documents | |
| Insert the rest after a duplicate key error, then report it | |
| New ObjectId | |
| When an ObjectId was created | |
| Current time as a BSON date | |
| Date from an ISO string | |
| Exact decimal for money, no float rounding | |
| Mix inserts, updates and deletes in one call |
A second insert with the same or unique key fails with E11000 duplicate key error.
Example: seed the and collections used on the rest of this page.
use shop
db.users.insertMany([
{ _id: 7, email: "[email protected]", name: "Ana Ruiz", address: { city: "Berlin" }, signup_at: ISODate("2026-01-10") },
{ _id: 8, email: "[email protected]", name: "Ben Okafor", address: { city: "Lagos" }, signup_at: ISODate("2026-03-02") }
])
db.orders.insertMany([
{ order_no: 1001, user_id: 7, status: "paid", total: 249.9, paid: 249.9, tags: ["gift"],
items: [{ sku: "SKU-42", qty: 2, price: 99.95 }, { sku: "SKU-7", qty: 1, price: 50 }],
created_at: ISODate("2026-10-01T09:12:00Z") },
{ order_no: 1002, user_id: 8, status: "pending", total: 89, paid: 0, tags: [],
items: [{ sku: "SKU-7", qty: 1, price: 89 }],
created_at: ISODate("2026-08-14T16:40:00Z") },
{ order_no: 1003, user_id: 7, status: "shipped", total: 610, paid: 600, tags: ["gift", "express"],
items: [{ sku: "SKU-42", qty: 6, price: 101.67 }],
created_at: ISODate("2026-10-05T11:00:00Z"), shipped_at: ISODate("2026-10-06T08:30:00Z") }
])
Find and Query Operators
takes a filter document. Fields listed side by side are ANDed together.
| Task | Query |
|---|---|
| All documents (SQL ) | |
| First match only | |
| By ObjectId | |
| Equal (SQL ) | |
| Not equal (SQL ) | |
| Range (, , , ) | |
| Any of several values (SQL ) | |
| None of several values (SQL ) | |
| AND | |
| OR | |
| Date greater than | |
| Field has a value (SQL ) | |
| Null or missing (SQL ) | |
| Field missing entirely | |
| Contains, any case (SQL ), reads every key | |
| Starts with (SQL ), can use an index | |
| Nested field | |
| Array contains a value | |
| Array contains all values | |
| Array is not empty | |
| Array has exactly N items | |
| One array element meets two conditions | |
| Compare two fields | |
| Wrong type, e.g. totals stored as strings | |
| Count matches (SQL ) | |
| Distinct values (SQL ) |
Without , matches order 1001, where SKU-7 has qty 1 and SKU-42 has qty 2.
Example: Berlin users' paid or shipped orders over 100 from October, excluding express orders.
const berlin = db.users.distinct("_id", { "address.city": "Berlin" })
db.orders.find({
user_id: { $in: berlin },
status: { $in: ["paid", "shipped"] },
total: { $gt: 100 },
created_at: { $gte: ISODate("2026-10-01"), $lt: ISODate("2026-11-01") },
tags: { $ne: "express" }
})
Sort, Limit and Projection
| Task | Query |
|---|---|
| Return only some fields (SQL ) | |
| Return everything except one field | |
| Only the matching array element | |
| Sort descending (SQL ) | |
| Sort by two fields | |
| First N (SQL ) | |
| Skip N (SQL ) | |
| Next page after the last seen | |
| Show the next batch of results | |
| Show more documents per batch | |
| Results as an array, for scripts |
Example: the five most recent orders with just the order number, status and total.
db.orders.find({}, { _id: 0, order_no: 1, status: 1, total: 1 })
.sort({ created_at: -1 })
.limit(5)
Update and Delete
and need an operator such as . To swap the whole document, use .
| Command | What it does |
|---|---|
| Set fields on the first match | |
| Set fields on every match | |
| Remove a field | |
| Add to a number | |
| Set a field to the current time | |
| Append to an array | |
| Append only if not already present | |
| Remove matching values from an array | |
| Update the array element that matched | |
| Rename a field in every document | |
| Update, or insert if nothing matches | |
| Update and return the new version | |
| Set a field from other fields (pipeline update) | |
| Replace the whole document, keeps | |
| DestructiveDelete the first match | |
| DestructiveDelete every match | |
| DestructiveDelete the oldest match and return it | |
| DestructiveDelete every document, keeps the indexes |
Example: upsert a user so the signup date is written only on the first insert, then mark old pending orders cancelled.
db.users.updateOne(
{ email: "[email protected]" },
{ $set: { name: "Cara Lind" }, $setOnInsert: { signup_at: new Date() } },
{ upsert: true }
)
db.orders.updateMany(
{ status: "pending", created_at: { $lt: new Date(Date.now() - 30 * 24 * 3600 * 1000) } },
{ $set: { status: "cancelled" }, $currentDate: { updated_at: true } }
)
Aggregation
runs the stages in order, each one feeding the next.
| Task | Stage |
|---|---|
| Filter (SQL ), put it first so it can use an index | |
| Group and count (SQL ) | |
| Group by multiple fields | |
| Sum, average, min, max | |
| Filter on a group result (SQL ) | after |
| Sort (SQL ) | |
| Limit and skip | , |
| Pick and compute fields (SQL ) | |
| Add a field, keep the rest | |
| One document per array element | |
| Join another collection (SQL ) | |
| Count the documents (SQL ) | |
| Count per value, sorted | |
| Group by day | |
| Collect values into an array | |
| Latest document per group | , then with , see below |
| Several aggregations in one pass | , see below |
| Write the result to a collection | or |
Latest order per user, using after a sort:
db.orders.aggregate([
{ $sort: { created_at: -1 } },
{ $group: { _id: "$user_id", last_order: { $first: "$order_no" }, at: { $first: "$created_at" } } }
])
with a pipeline joins on extra conditions. passes fields from the outer document, used as :
db.users.aggregate([
{ $lookup: {
from: "orders",
let: { uid: "$_id" },
pipeline: [
{ $match: { $expr: { $eq: ["$user_id", "$$uid"] }, status: { $ne: "cancelled" } } },
{ $sort: { created_at: -1 } },
{ $limit: 3 }
],
as: "recent_orders"
} }
])
runs sub-pipelines over the same input, handy for a count plus one page of results:
db.orders.aggregate([
{ $match: { status: { $in: ["paid", "shipped"] } } },
{ $facet: {
total: [{ $count: "n" }],
by_status: [{ $sortByCount: "$status" }],
page: [{ $sort: { created_at: -1 } }, { $limit: 10 }]
} }
])
In 1bench: the MongoDB GUI builds pipelines stage by stage, with forms for , , , and the other common stages, and a raw JSON mode for anything else.
Example: revenue and units per SKU for paid and shipped orders, joined to the buyer's email.
db.orders.aggregate([
{ $match: { status: { $in: ["paid", "shipped"] } } },
{ $lookup: { from: "users", localField: "user_id", foreignField: "_id", as: "user" } },
{ $unwind: "$user" },
{ $unwind: "$items" },
{ $group: {
_id: "$items.sku",
units: { $sum: "$items.qty" },
revenue: { $sum: { $multiply: ["$items.qty", "$items.price"] } },
buyers: { $addToSet: "$user.email" }
} },
{ $sort: { revenue: -1 } }
])
Indexes
| Command | What it does |
|---|---|
| Compound index; field order matters | |
| Reject duplicate values | |
| TTL index, deletes documents an hour after | |
| Index only the documents matching a filter | |
| Text index for search | |
| Search the text index | |
| List indexes with their keys and options | |
| Show the plan, docs examined and time taken | |
| How often each index has been used | |
| Hide an index from the planner before dropping it | |
| Bytes used by all indexes on the collection | |
| Drop one index by name | |
| Drop every index except |
In output, means an index was used and means every document was read. Compare with .
In 1bench: the MongoDB GUI shows each collection's indexes with their unique, sparse and TTL settings next to document count and storage size, and creates indexes from a form.
Example: index orders for "this user's latest orders" and confirm the query uses it.
db.orders.createIndex({ user_id: 1, created_at: -1 })
const plan = db.orders.find({ user_id: 7 }).sort({ created_at: -1 }).explain("executionStats")
plan.queryPlanner.winningPlan // look for IXSCAN, no SORT stage
plan.executionStats.totalDocsExamined // should equal nReturned
Scripts
mongosh runs JavaScript, so a script is a file with the same commands you type in the shell.
| Command | What it does |
|---|---|
| Run a script file and exit | |
| Same with an explicit flag | |
| Run a script, then stay in the shell | |
| Run a script from inside an open shell | |
| Evaluate one expression, print the result | |
| Print the result as Extended JSON | |
| / | Print from a script |
| Serialize a document with types kept | |
| Read an environment variable | |
| Pause for one second | |
| Runs on every interactive start (skip with ) |
Example: a nightly script that archives orders older than a year and prints how many it moved.
// archive-orders.js, run with: mongosh "mongodb://localhost/shop" archive-orders.js
const cutoff = new Date(Date.now() - 365 * 24 * 3600 * 1000)
db.orders.aggregate([
{ $match: { created_at: { $lt: cutoff } } },
{ $merge: { into: "orders_archive", whenMatched: "keepExisting" } }
])
const res = db.orders.deleteMany({ created_at: { $lt: cutoff } })
print(`archived ${res.deletedCount} orders`)
Backup and Restore
These ship separately from the server as the MongoDB Database Tools. writes BSON; writes JSON or CSV.
| Command | What it does |
|---|---|
| Dump one database to a folder of BSON files | |
| Dump every database | |
| Dump one collection | |
| Dump into one compressed file | |
| Dump only matching documents (Extended JSON) | |
| Restore the folder; inserts only, existing s fail | |
| DestructiveDrop each collection before restoring it | |
| Restore into a database with a new name | |
| Restore one collection from a full dump | |
| Import a JSON array file | |
| Import CSV, first line has the field names | |
| Update existing documents by a key, insert the rest | |
| Export one JSON document per line | |
| Export chosen fields as CSV |
Example: copy production's database into a local database through one compressed archive.
mongodump --uri "mongodb://[email protected]:27017/shop?authSource=admin" --archive=shop.gz --gzip
mongorestore --uri "mongodb://localhost:27017" --archive=shop.gz --gzip \
--nsFrom 'shop.*' --nsTo 'shop_staging.*'
Users and Roles
Users live in the database they were created in, usually . Logins then need .
| Command | What it does |
|---|---|
| Create a user, prompting for the password | |
| Users defined in the current database | |
| One user's roles and resulting privileges | |
| Built-in and custom roles in the current database | |
| Add a role | |
| Remove a role | |
| Set a new password | |
| Who am I: the logged-in user and roles | |
| Delete a user | |
| Start the server with access control on |
Common built-in roles are , , and per database, plus and in .
Example: create a read-only reporting user in that can read and .
use admin
db.createUser({
user: "reporting",
pwd: passwordPrompt(),
roles: [{ role: "read", db: "shop" }, { role: "read", db: "analytics" }]
})
db.getUser("reporting")
Server and Monitoring
| Command | What it does |
|---|---|
| Server version | |
| Node role, primary host and replica set name | |
| Replica set members, state and replication lag | |
| Turn a fresh node into a one-member replica set | |
| Current and available connections | |
| Operations running for 5 seconds or more | |
| Stop an operation by its | |
| Log queries slower than 100 ms to | |
| Latest slow queries | |
| Startup options, config file and | |
| Read and write time per collection every 5 seconds | |
| Inserts, queries, updates and connections per second | |
| DestructiveStop the server, run from |
Example: find queries that have been running for more than 10 seconds on and stop them.
db.currentOp({ secs_running: { $gt: 10 }, ns: /^shop\./ }).inprog.forEach(op => {
print(`${op.opid} ${op.secs_running}s ${op.ns}`)
db.killOp(op.opid)
})
Gotchas
- matches documents where the field is null and documents that do not have it at all. Add when you only want explicit nulls.
- Types must match. does not find , and does not find an ObjectId. Wrap it in .
- removes every document but keeps the collection and its indexes, while removes both. Neither can be undone without a backup.
- fails in mongosh because the update has no operator. with the same document replaces every field.
- only inserts. Documents whose already exists fail as duplicate keys and keep their current values, so a restore over live data silently mixes old and new. Restore into a new database with , or use .
- The legacy shell was removed in MongoDB 6.0, and its and helpers are deprecated in mongosh. Use and or .