Document Limits

Document Limits and Storage Rules

The rules below are enforced by the server, not the driver, so you find out at write time.

Hard limits every MongoDB 1,815 document must respect
Rule Limit
BSON document size 16 MiB (16,777,216 bytes)
Nesting depth 100 levels; each object or array counts
Database name under 64 bytes
Namespace db.collection 255 bytes, or 235 bytes when sharded
Indexes per collection 64, at most 32 fields in one compound index
Field names may not contain the null character

The 16 MiB ceiling shapes designs: it exists so a document always fits in memory and on the wire without streaming, and it covers pipeline output as well as stored documents.

What the limits cost you in practiceJavaScript
import { calculateObjectSize, serialize, Binary } from 'bson';
const LIMIT = 16 * 1024 * 1024;
console.log('over limit  :', serialize({ blob: new Binary(Buffer.alloc(LIMIT)) }).length);
const short = {}, long = {};
for (let i = 0; i < 1000; i++) { short['v' + i] = 20.5; long['measured' + i] = 20.5; }
console.log('short names :', calculateObjectSize(short), '| long names :',
  calculateObjectSize(long));
Output
over limit  : 16777232
short names : 13895 | long names : 20895

The first line is the warning: the bson library happily serialized a document larger than 16 MiB. Nothing client-side stops you — the server returns BSONObjectTooLarge when the write arrives. Check sizes yourself with calculateObjectSize, or Object.bsonsize(doc) in mongosh 403 .

Field names are stored in full in every document, which is what the second line measures: the same 1,000 values cost 13,895 bytes named v0-style and 20,895 named measured0-style, half as much again. Across hundreds of millions of documents that is real money, and it is why time series collections (Time Series Collections) store measurements differently. Elsewhere readable names win — but never repeat the collection name in a field (bookTitle inside books).

Since MongoDB 5.0 the server stores field names containing . and $, so a document scraped from an external API need not be mangled on the way in; querying them needs $getField and $setField, which makes the support an escape hatch rather than a naming style.