BSON Data Types and ObjectId

Beginner
11 min

BSON Data Types and ObjectId

JSON knows only six value types, which is not enough for a database: it has no date, no binary data, no distinction between integers and floating-point numbers, and no built-in unique identifier. MongoDB stores documents as BSON (Binary JSON), a typed binary format that fills those gaps. After this lesson you will be able to store values with the right type from mongosh, recognize the anatomy of an ObjectId, query by type, and predict how MongoDB compares values of different types.

From JSON to BSON

BSON encodes every field with a type byte and a length prefix, so the server can skip fields it does not need. Every document is limited to 16 MB. The types you will use most:

| BSON type | $type alias | How to write it in mongosh | |---|---|---| | Double | double | 3.14, Double(2) | | 32-bit integer | int | 42, Int32(42) | | 64-bit integer | long | Long("9007199254740993") | | Decimal128 | decimal | Decimal128("19.99") | | String (UTF-8) | string | "text" | | Boolean | bool | true | | Date | date | new Date(), ISODate("2026-01-01") | | ObjectId | objectId | ObjectId() | | Array | array | [1, "a", { b: 2 }] | | Embedded document | object | { city: "Pune" } | | Null | null | null | | Binary data | binData | UUID() |

Numbers: int, long, double and decimal

JavaScript has a single number type, so the shell and the Node.js driver decide the BSON type for you: a plain integer that fits in 32 bits is stored as int, a value with a fractional part as double. Use the explicit constructors when it matters:

javascript
db.metrics.insertOne({ count: 42, // int ratio: 0.75, // double bigId: Long("9007199254740993"), // beyond Number.MAX_SAFE_INTEGER total: Decimal128("0.30") // exact decimal arithmetic }) db.metrics.find({ count: { $type: "int" } }) // matches db.metrics.find({ count: { $type: "number" } }) // alias for int, long, double, decimal

Use Decimal128 for money. A double cannot represent 0.1 exactly, so summing prices as doubles produces values like 0.30000000000000004. Decimal128 stores 34 significant digits exactly and works with $sum, $multiply and comparison operators.

Dates

A BSON Date is a 64-bit count of milliseconds since the Unix epoch, always in UTC. Store dates as Date, never as strings: only real dates support range queries, date operators and TTL indexes.

javascript
db.orders.insertOne({ placedAt: new Date() }) // now, from the client clock db.orders.insertOne({ placedAt: ISODate("2026-02-15T10:30:00Z") }) db.orders.find({ placedAt: { $gte: ISODate("2026-02-01"), $lt: ISODate("2026-03-01") } })

Note that Date.now() returns a plain number, not a Date; inserting it stores a long, and date queries will silently miss it.

Anatomy of an ObjectId

Every document needs a unique _id. If you do not supply one, the driver generates a 12-byte ObjectId on the client before the insert is sent, so no round trip is needed to learn the new id:

| Bytes | Content | |---|---| | 0–3 | seconds since the Unix epoch | | 4–8 | random value unique to the process | | 9–11 | incrementing counter, randomly seeded |

javascript
const id = ObjectId() // ObjectId('67c1f0e8a1b2c3d4e5f60718') id.getTimestamp() // ISODate('2026-02-28T14:52:56.000Z') db.users.find({ _id: ObjectId("67c1f0e8a1b2c3d4e5f60718") })

Because the timestamp comes first, sorting by _id approximates insertion order, and _id gets a unique index automatically. _id does not have to be an ObjectId: a SKU string, a numeric code or an embedded document all work, as long as the value is unique and never an array.

How MongoDB Compares Different Types

When a field holds different types across documents, sorting and range queries first order by type and only then by value. The canonical order is: MinKey, Null, Numbers, String, Object, Array, BinData, ObjectId, Boolean, Date, Timestamp, Regular Expression, MaxKey. Numeric types compare by value, and a missing field sorts as null.

javascript
db.mixed.insertMany([{ v: "10" }, { v: 9 }, { v: true }, { v: null }]) db.mixed.find().sort({ v: 1 }) // null, 9, "10", true db.mixed.find({ v: { $gt: 5 } }) // only 9 — strings are never compared with numbers

Common Mistakes

  • Storing dates or numbers as strings. "2026-02-15" and "100" cannot be used in range queries or arithmetic without conversion.
  • Comparing a string with an ObjectId. find({ _id: "67c1..." }) returns nothing; wrap the value with ObjectId().
  • Using doubles for currency. Choose Decimal128 or store integer paise/cents.
Quick Quiz
Question 1 of 3

Which BSON type should you use for a product price of 19.99?

Key Takeaways

  • BSON adds types JSON lacks: Date, ObjectId, 32/64-bit integers, Decimal128, binary and regex.
  • Small integers are stored as int, fractions as double; use Long(), Decimal128() and Double() to be explicit.
  • Store dates as Date values so range queries, date operators and TTL indexes work.
  • ObjectId is 12 bytes: a timestamp, a random process value and a counter, generated on the client.
  • Mixed-type fields sort by type first, then by value; $type lets you find and clean them up.

Next lesson: Inserting Documents — add single and multiple documents with insertOne() and insertMany() and understand what the server returns.

BSON Data Types and ObjectId - MongoDB | CodeYourCraft | CodeYourCraft