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.
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() |
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:
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, decimalUse 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.
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.
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.
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 |
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.
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.
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"2026-02-15" and "100" cannot be used in range queries or arithmetic without conversion.find({ _id: "67c1..." }) returns nothing; wrap the value with ObjectId().Decimal128 or store integer paise/cents.Which BSON type should you use for a product price of 19.99?
Date, ObjectId, 32/64-bit integers, Decimal128, binary and regex.int, fractions as double; use Long(), Decimal128() and Double() to be explicit.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.$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.