mongosh configuration, scripting and legacy features to replace
Make mongosh yours with config and .mongoshrc.js, run scripts safely from the command line with EJSON output, and replace the features that old code and old tutorials still use: mapReduce, server-side JavaScript, count(), hedged reads and the legacy mongo shell.
- Change shell settings with config.set and a .mongoshrc.js file
- Run scripts with --file, --eval and --quiet and print EJSON
- Rewrite mapReduce and $where as aggregation and $expr
- Recognise removed and deprecated features in old runbooks
conceptThe concept
Shell settings. config holds mongosh settings: config.set("displayBatchSize", 50) changes how many documents are shown before "it", maxTimeMS applies a time limit to every operation from the shell, inspectDepth controls how deep nested objects are printed, and editor picks the editor for edit. Settings persist across sessions. A global file /etc/mongosh.conf can set defaults for every user.
Startup script. ~/.mongoshrc.js runs at startup (skip it with --norc). Teams use it for a prompt that shows the replica set member and role, and for helper functions. Keep it free of anything that writes.
Scripts. mongosh --file script.js or load("script.js") runs a file; --eval runs a string; --quiet drops the banner. For machine-readable output, print EJSON.stringify(value): plain JSON loses types (dates become strings, ObjectIds become objects), and Extended JSON keeps them. { relaxed: false } gives the canonical form, with every number typed.
Legacy features. mapReduce is deprecated since 5.0; aggregation does the same work faster and is the only path forward. Server-side JavaScript ($where, $function, $accumulator) is deprecated in 8.0; most uses become $expr or ordinary operators. count() is replaced by countDocuments() (accurate, uses the query) and estimatedDocumentCount() (metadata, fast). Hedged reads are deprecated in 8.0. The legacy mongo shell was removed in 6.0, and db.eval, copydb and the group command were removed in 4.2.
whyWhy it matters
Old runbooks, Stack Overflow answers and inherited scripts still use mapReduce, $where and the mongo shell. They fail on upgrade or run far slower than they should. Knowing the replacements turns a blocked upgrade into a short rewrite. Shell configuration and safe scripting are what separate an ad hoc console session from a repeatable, reviewable operation.
Use caseReal-world scenario
An upgrade to 8.0 is blocked by a reporting script that uses mapReduce to total revenue per city and a $where filter for "orders where the total is more than the sum of the items". The DBA rewrites the first as a $group pipeline and the second as $expr with $sum over the items, runs both old and new on a copy to compare results, and schedules the new script with mongosh --quiet --file, printing EJSON for the downstream job.
Try itExamples
Run and edit these in the interactive lesson.
config
config.set("displayBatchSize", 5)
db.products.find({}, { _id: 0, name: 1 })
config.reset("displayBatchSize")const o = db.orders.findOne({}, { orderNo: 1, createdAt: 1, total: 1 })
JSON.stringify(o)
EJSON.stringify(o)
EJSON.stringify(o, { relaxed: false })EJSON.parse('{"at":{"$date":"2026-01-15T10:00:00Z"},"qty":{"$numberInt":"3"}}')db.orders.aggregate([
{ $match: { status: "delivered" } },
{ $group: { _id: "$shipping.city", revenue: { $sum: "$total" }, orders: { $sum: 1 } } },
{ $sort: { revenue: -1 } },
{ $limit: 3 }
])db.orders.mapReduce(
function () { if (this.status === "delivered") emit(this.shipping.city, { revenue: this.total, orders: 1 }); },
function (key, values) { return values.reduce((a, v) => ({ revenue: a.revenue + v.revenue, orders: a.orders + v.orders }), { revenue: 0, orders: 0 }); },
{ out: { inline: 1 } }
)const viaWhere = db.orders.find({ $where: function () { return this.total > 2500; } }).count()
const viaExpr = db.orders.countDocuments({ $expr: { $gt: ["$total", 2500] } })
const viaQuery = db.orders.countDocuments({ total: { $gt: 2500 } })
;({ viaWhere, viaExpr, viaQuery });({ exact: db.orders.countDocuments({ status: "cancelled" }), estimated: db.orders.estimatedDocumentCount(), all: db.orders.countDocuments({}) })// ~/.mongoshrc.js
prompt = () => {
const h = db.hello();
const role = h.isWritablePrimary ? "PRIMARY" : (h.secondary ? "SECONDARY" : "OTHER");
return `${h.setName || "standalone"}:${role} ${db.getName()}> `;
};
config.set("inspectDepth", 8);
# command line
mongosh "mongodb://ops@db1:27017/?replicaSet=rs0&authSource=admin" --quiet --norc --file nightly-report.js
mongosh "$URI" --quiet --eval 'EJSON.stringify(db.orders.findOne())'
# /etc/mongosh.conf (YAML) sets defaults for every user
mongosh:
displayBatchSize: 50
enableTelemetry: falseinternalsUnder the hood
mapReduce and server-side JavaScript run in a JavaScript engine inside mongod, one document at a time, which is slower than native aggregation operators and cannot use the slot-based execution engine. Disabling server-side scripting (security.javascriptEnabled: false) removes a class of injection risk once no code depends on it.
count() without a filter reads collection metadata, which can be wrong after an unclean shutdown and counts orphaned documents on sharded clusters. That is why drivers split it into an exact countDocuments (an aggregation) and an explicit estimatedDocumentCount.
Avoid / preferDo this, not that
db.orders.find({ $where: "this.total > this.items.length * 100" })db.orders.find({ $expr: { $gt: ["$total", { $multiply: [{ $size: "$items" }, 100] }] } })$where runs JavaScript on every document and is deprecated in 8.0. $expr is native, faster and works with indexes for the parts of the filter that can use them.
mongosh "$URI" --eval 'printjson(db.orders.findOne())' > out.json
mongosh "$URI" --quiet --eval 'EJSON.stringify(db.orders.findOne())' > out.json
printjson output is shell formatting, not valid JSON, and loses types. EJSON is valid JSON that keeps them, and --quiet keeps the banner out of the file.
PitfallsCommon mistakes
- Keeping mapReduce jobs "because they still work" and discovering the deprecation during an upgrade.
- Using $where for comparisons between fields, which $expr does natively.
- Printing JSON.stringify output from scripts and losing dates and number types downstream.
- Putting writes or connection strings with passwords in .mongoshrc.js.
- Following runbooks written for the legacy mongo shell, whose helpers and output formats differ.
LimitsWhen not to use it
Do not build application logic on mongosh scripts; use a driver with proper error handling, retries and connection pooling. Shell scripts are for administration and one-off reports. And do not rewrite working aggregation into JavaScript loops in the shell "for clarity": it moves all the data to the client.
SecuritySecurity notes
Never put passwords in .mongoshrc.js, in --eval strings or in connection strings on the command line, where they end up in shell history and process lists. Use a prompt, environment variables read by the script, or X.509 or LDAP authentication. mongosh keeps a history file; redactHistory (default "remove") strips lines that look like credentials. Disable server-side JavaScript once you no longer need it.
ChallengeTry it yourself
Replace this deprecated filter with an equivalent that uses no JavaScript, and return the count: orders where total is greater than 1000 and the order has more than 2 items. $where: function () { return this.total > 1000 && this.items.length > 2 }.
Show one solution
const viaExpr = db.orders.countDocuments({ total: { $gt: 1000 }, $expr: { $gt: [{ $size: "$items" }, 2] } })
const viaWhere = db.orders.find({ $where: function () { return this.total > 1000 && this.items.length > 2; } }).count()
;({ viaExpr, same: viaExpr === viaWhere })InterviewQuestions you should be able to answer
How do you migrate a mapReduce job to the aggregation framework?
Translate the map function's filter into $match and its emitted key and value into $project or $group; the reduce function becomes accumulators such as $sum, $push or $mergeObjects; finalize becomes a later $project; and out becomes $merge (incremental, like out: reduce or merge) or $out (replace). Run both on the same snapshot and compare results, check memory use (allowDiskUse), then switch.
What replaces server-side JavaScript in queries?
Almost always $expr with aggregation operators: field-to-field comparisons, arithmetic, string and array functions, and conditions. Custom accumulators usually become $group with $push followed by array operators, or $setWindowFields. Once nothing depends on JavaScript, set security.javascriptEnabled: false to remove the attack surface.
QuizCheck your understanding
5 questions with instant feedback in the interactive lesson; a wrong answer goes into your review deck.