MongoDBDocuments & BSON

Documents & BSON

MongoDB stores data as BSON (Binary JSON) — a binary-encoded serialization of JSON-like documents. BSON was designed specifically for MongoDB to be lightweight, traversable, and efficient for both encoding and decoding.

BSON extends JSON with additional data types that JSON cannot represent natively: ObjectId, Date, Binary, Decimal128, Timestamp, RegExp, and more. This richer type system enables precise numeric arithmetic, efficient date range queries, and embedded binary data — all without the ambiguity of string encoding.

Every MongoDB document is a BSON object: an ordered set of key-value pairs stored as a contiguous binary blob.

What is a Document?

A document is the fundamental unit of data in MongoDB — equivalent to a row in a relational database, but far more flexible. Documents are self-describing: they carry their own field names alongside their values, and any document in a collection can have a different structure.

A complete MongoDB document

JSON
{
  "_id":          { "$oid": "64f1a2b3c4d5e6f7a8b9c0d1" },
  "name":         "Alice Müller",
  "age":          28,
  "email":        "alice@example.com",
  "verified":     true,
  "score":        { "$numberDecimal": "9.87" },
  "createdAt":    { "$date": "2024-01-15T09:30:00.000Z" },
  "tags":         ["admin", "beta-tester", "power-user"],
  "address": {
    "street":     "123 Main St",
    "city":       "Berlin",
    "country":    "DE",
    "postalCode": "10115"
  },
  "profileImage": { "$binary": { "base64": "", "subType": "00" } },
  "deletedAt":    null
}
BSON Data Types

BSON Type

JavaScript Type

Description

Example

String

String

UTF-8 encoded text. The most common type for human-readable values.

"Alice"

Integer (32-bit)

Number

Whole numbers up to ±2 147 483 647. MongoDB uses 32-bit by default for small integers.

42

Integer (64-bit)

Number / BigInt

Whole numbers up to ±9.2 × 10¹⁸. Required for timestamps and large counters.

NumberLong("9007199254740993")

Double

Number

64-bit IEEE 754 floating-point. Default for fractional numbers in JavaScript.

3.14

Decimal128

Decimal128

High-precision 128-bit decimal. Use for currency and financial calculations to avoid floating-point drift.

NumberDecimal("9.99")

Boolean

Boolean

True or false.

true

Date

Date

UTC milliseconds since Unix epoch. Always store dates as Date — never as strings.

new Date()

ObjectId

ObjectId

12-byte unique identifier auto-generated for _id fields. Embeds a creation timestamp.

ObjectId()

Array

Array

Ordered list of values of any BSON type, including nested arrays and documents.

["a", "b", 3]

Object (Embedded Document)

Object

A nested BSON document. Enables denormalized, self-contained records.

{ city: "Berlin" }

Null

null

Explicit absence of value. Distinct from a missing field.

null

Regular Expression

RegExp

A PCRE regex pattern with optional flags. Used in query filters.

/^alice/i

Binary Data

Buffer / BinData

Raw bytes. Used for file contents, UUIDs, hashes, and encrypted payloads.

BinData(0, "...")

Timestamp

Timestamp

64-bit internal type used by the replication oplog. Not for application use — use Date instead.

Timestamp()

Symbol

Symbol

Deprecated. Identical to String for modern drivers. Avoid in new schemas.

MinKey / MaxKey

MinKey / MaxKey

Special comparison values that sort lower/higher than all other BSON types. Used in sharding.

MinKey()

The _id Field

Every MongoDB document must have an _id field, which serves as the document's primary key. The _id field:

  • Must be unique within its collection.
  • Is immutable — you cannot change it after insertion.
  • Is automatically indexed — MongoDB creates a unique index on _id for every collection.
  • Is auto-generated as a 12-byte ObjectId if you don't supply one.

ObjectId structure

JS
// Create a new ObjectId
const id = new ObjectId()
id.toString()          // "64f1a2b3c4d5e6f7a8b9c0d1"  (24 hex chars)
id.getTimestamp()      // Date object representing when the ObjectId was generated

// An ObjectId encodes:
//  ┌──────────────┬─────────────────────┬──────────────────┐
//  │  4 bytes     │  5 bytes            │  3 bytes         │
//  │  Unix epoch  │  Random (process +  │  Auto-increment  │
//  │  (seconds)   │  machine entropy)   │  counter         │
//  └──────────────┴─────────────────────┴──────────────────┘
//
// The timestamp component means ObjectIds sort chronologically
// and you can extract an approximate creation time for free.

// Extract timestamp from an existing ObjectId
ObjectId("64f1a2b3c4d5e6f7a8b9c0d1").getTimestamp()
// ISODate("2023-09-01T10:22:43.000Z")
Custom _id Values

You are not required to use ObjectId — any BSON value that is unique within the collection is valid as an _id:

Documents with custom _id

JS
// String _id — useful when a natural key exists (e.g., username, ISO code)
db.countries.insertOne({ _id: "DE", name: "Germany", population: 83_200_000 })

// Integer _id — suitable for small, controlled datasets
db.settings.insertOne({ _id: 1, theme: "dark", language: "en" })

// UUID _id — for systems that generate UUIDs externally
const { UUID } = require("bson")
db.sessions.insertOne({ _id: new UUID("550e8400-e29b-41d4-a716-446655440000"), userId: "abc" })

// Composite _id — an embedded document acting as a compound primary key
db.inventory.insertOne({
  _id: { warehouse: "NYC", sku: "WIDGET-42" },
  quantity: 500,
  reserved: 12
})
Warning
The _id field is immutable after insertion. If you need to change a document's identifier, you must delete the old document and insert a new one. Attempting to $set the _id field throws an ImmutableField error. Inserting a duplicate _id throws E11000 duplicate key error.
Embedded Documents (Sub-documents)

Documents can contain other documents nested to any depth. This is the core strength of the document model: instead of joining multiple tables at query time, you store related data together in a single document.

Embedding is ideal when the nested data is:

  • Always accessed together with the parent document.
  • Owned exclusively by one parent (no sharing).
  • Finite and bounded in size.

Nested document example

JS
db.orders.insertOne({
  _id: ObjectId(),
  orderNumber: "ORD-2024-00142",
  status: "shipped",
  // Embedded customer — no join needed to display the order
  customer: {
    _id: ObjectId("64abc123..."),
    name: "Alice Müller",
    email: "alice@example.com"
  },
  // Embedded shipping address — snapshot at time of order
  shippingAddress: {
    street: "123 Main St",
    city: "Berlin",
    postalCode: "10115",
    country: "DE"
  },
  // Array of embedded line items
  items: [
    { sku: "WIDGET-42", name: "Blue Widget", qty: 2, unitPrice: 9.99 },
    { sku: "GADGET-7",  name: "Red Gadget",  qty: 1, unitPrice: 24.50 }
  ],
  subtotal: 44.48,
  tax: 8.45,
  total: 52.93,
  createdAt: new Date(),
  shippedAt: null
})
Arrays in Documents

Arrays are first-class citizens in MongoDB. You can store ordered lists of any BSON type — strings, numbers, embedded documents, or even other arrays — directly within a document field. MongoDB provides a rich set of array query and update operators specifically for working with array fields.

Array field types

JS
// Array of strings
{ tags: ["javascript", "nodejs", "mongodb"] }

// Array of numbers
{ scores: [95, 87, 92, 78, 100] }

// Array of embedded documents (the most powerful pattern)
{
  comments: [
    { author: "Bob",   text: "Great post!",  likes: 5,  postedAt: ISODate("2024-01-10") },
    { author: "Carol", text: "Very helpful.", likes: 12, postedAt: ISODate("2024-01-11") }
  ]
}

// Mixed-type array (valid BSON, but query complex — avoid in practice)
{ misc: [42, "hello", true, null, { key: "val" }] }

// Querying into arrays
db.posts.find({ tags: "nodejs" })               // match if array contains "nodejs"
db.posts.find({ scores: { $gt: 90 } })          // match if any element > 90
db.posts.find({ "comments.author": "Bob" })     // dot notation into embedded array docs
Document Size Limits
Note
A single BSON document cannot exceed 16 MB. For larger binary data (images, videos, PDFs), use GridFS — MongoDB's built-in file storage system that splits large files into 255 KB chunks and stores them in two collections (fs.files and fs.chunks).

Limit

Value

Notes

Max document size

16 MB

Includes all embedded documents and array elements. Use GridFS for larger binary data.

Max BSON nesting depth

100 levels

Deeply nested structures degrade query performance and readability. Keep nesting shallow.

Max field name length

No hard limit

Field names are stored in every document. Shorter names (e.g., "ts" vs "timestamp") save significant space in large collections.

Max index key size

1 024 bytes

Strings used as index keys are subject to this limit (pre-4.2 stricter limits may apply).

Max documents per collection

Unlimited (practical limit: billions)

Performance depends on indexes, hardware, and access patterns — not a hard document count cap.

Working with Dates

MongoDB stores dates as 64-bit integers representing milliseconds since the Unix epoch (UTC). The Date type supports native comparison operators, sorting, aggregation arithmetic, and TTL indexes (automatic document expiration).

Date handling in MongoDB

JS
// Storing dates — always use new Date() or ISODate()
db.events.insertOne({
  name: "Conference",
  startsAt: new Date("2024-06-15T09:00:00Z"),
  endsAt:   ISODate("2024-06-17T18:00:00Z"),
  createdAt: new Date()   // current UTC time
})

// Querying by date range
db.events.find({
  startsAt: {
    $gte: new Date("2024-01-01"),
    $lt:  new Date("2025-01-01")
  }
})

// Sorting chronologically (ascending = earliest first)
db.events.find().sort({ startsAt: 1 })

// Extracting date parts in aggregation
db.events.aggregate([
  {
    $project: {
      year:  { $year: "$startsAt" },
      month: { $month: "$startsAt" },
      day:   { $dayOfMonth: "$startsAt" }
    }
  }
])
Tip
Always use new Date() or ISODate() for date fields — never store dates as strings. Proper Date types enable range queries, chronological sorting, TTL index expiration, and aggregation date arithmetic. String dates require complex parsing and sort lexicographically, not chronologically.