MongoDB Data Types

Choose the right BSON type for each field: strings, 32 and 64-bit integers, doubles, Decimal128 for money, booleans, dates, ObjectIds, arrays, embedded documents and null, and check or convert types with $type and $convert.

Lesson 8 of 43Beginner18 min read

MongoDB documents look like JSON, but underneath they're stored as BSON, which has more types than JSON. JSON has a single kind of number; BSON has 32-bit integers, 64-bit integers, double-precision floating point and exact decimals. JSON has no dates; BSON has a real Date type. And BSON has ObjectId, the 12-byte identifier MongoDB generates for _id.

Types matter more than they first appear. A query for { price: 10 } won't find a price stored as the string "10". Strings sort alphabetically, so "100" comes before "20". Floating-point numbers can't represent most decimal fractions exactly, so 0.1 + 0.2 isn't exactly 0.3, which is a problem for money. Dates stored as text can't be compared or used in date arithmetic.

In this lesson you'll meet each common BSON type, learn which to use for prices, counts, timestamps and identifiers, see how MongoDB compares and sorts mixed types, query by type with $type, and convert badly typed data with aggregation conversion operators.

The common BSON types

Typemongosh exampleUse for
String'Leeds'Text, codes, emails
Double8.5Measurements, ratings, anything approximate
Int32 / Int64NumberInt(5), NumberLong('9000000000')Counts, quantities, ids
Decimal128Decimal128('19.99')Money and exact decimals
BooleantrueFlags
DateISODate('2026-03-01T10:00:00Z'), new Date()Timestamps, always stored in UTC
ObjectIdObjectId()Generated identifiers
Array['a', 'b']Lists of values or documents
Object{ city: 'York' }Embedded documents
NullnullAn explicit "no value"

In mongosh, a whole number like 5 is stored as a 32-bit int, while a number with a decimal point, such as 5.5, or one too large for 32 bits is stored as a double. (The old mongo shell stored every plain number as a double, so you'll still see that in older tutorials.) Drivers in typed languages pick integer or double types from your code.

Numbers and money

Doubles are fast and fine for measurements, but they store binary approximations:

MongoDB Shell
0.1 + 0.2              // 0.30000000000000004

For prices, totals and balances, use Decimal128, which stores decimal digits exactly, or store whole pennies as integers (priceInPence: 1999). Both keep sums accurate. When a field mixes types, such as some prices as doubles and some as strings, comparisons and totals quietly go wrong, so pick one type per field and validate it.

Dates, ObjectIds and type checks

  • Dates are stored as milliseconds since 1970 in UTC. Convert to local time only when displaying.
  • An ObjectId contains its creation time: ObjectId('...').getTimestamp().
  • $type finds documents by type, by name or number: { price: { $type: 'string' } } or { price: { $type: 'number' } } (any numeric type).
  • When comparing values of different types, MongoDB uses a fixed order: missing and null, then numbers, then strings, then objects, arrays, ObjectIds, booleans and dates. That's why a string price never matches { price: { $gt: 5 } }.
  • Aggregation operators such as $toInt, $toDouble, $toDecimal, $toDate and $convert change types, which is useful for cleaning up imported data.

Types outside the database: Extended JSON

Plain JSON can't tell a date from a string or a decimal from a double, so when BSON data leaves MongoDB as text, through mongoexport, a REST API or a log file, the extra types need a representation. MongoDB uses Extended JSON (EJSON), which wraps special types in keys that start with $:

MongoDB Shell
EJSON.stringify({ price: Decimal128('19.99'), at: new Date('2026-03-01T10:00:00Z'), id: ObjectId('65f1c2a0e4b0a1b2c3d4e5f6') })
// {"price":{"$numberDecimal":"19.99"},"at":{"$date":"2026-03-01T10:00:00Z"},"id":{"$oid":"65f1c2a0e4b0a1b2c3d4e5f6"}}

EJSON.parse turns that text back into real BSON values, and mongoimport reads the same format, so a round trip keeps every type intact. Compare that with plain JSON.stringify, which turns the date into an ordinary string. Load that string back in and you've silently lost the Date type, which breaks sorting and range queries.

Extended JSON comes in two modes. Relaxed mode, the default, writes ordinary numbers as plain JSON numbers and dates as readable ISO strings inside $date. Canonical mode wraps every value, including {"$numberInt":"3"}, so even the difference between an int and a double survives. Use canonical mode when an exact copy matters more than readability.

When you build an API, you'll usually convert values yourself instead: send ids as strings, dates as ISO 8601 strings and decimals as strings, then convert them back with new ObjectId(), new Date() and Decimal128.fromString() on the way in.

MongoDB Basics Syntax

Types in mongosh

{ name: 'Mug', qty: NumberInt(3), views: NumberLong('5000000000') }
{ price: Decimal128('19.99'), rating: 4.5, active: true }
{ createdAt: ISODate('2026-03-01T10:00:00Z'), seenAt: new Date() }
{ _id: ObjectId(), tags: ['a'], address: { city: 'York' }, note: null }
db.c.find({ price: { $type: 'string' } })
db.c.find({ price: { $type: 'number' } })
ObjectId().getTimestamp()
db.c.aggregate([{ $set: { price: { $toDecimal: '$price' } } }])

MongoDB Basics Summary

  • BSON adds types to JSON: Int32, Int64, Double, Decimal128, Date, ObjectId and more.
  • Queries and sorting depend on types: the string "10" doesn't match the number 10.
  • Use Decimal128 or whole pennies for money; doubles are approximate.
  • Store dates as Date values in UTC; ObjectIds include a creation timestamp.
  • $type finds fields by type, and $convert and $toInt-style operators fix types.

MongoDB Basics Examples

Each example shows the code, the output it produces and what each part does.

Example 1

Types change what matches

MongoDB Shell
db.items.insertMany([
  { _id: 1, name: 'Cable', price: 10 },
  { _id: 2, name: 'Charger', price: '10' },
  { _id: 3, name: 'Case', price: 25.5 },
  { _id: 4, name: 'Strap', price: '100' }
])
db.items.find({ price: 10 }, { name: 1, _id: 0 })
db.items.find({ price: { $gt: 5 } }, { name: 1, _id: 0 })
db.items.find({ price: { $type: 'string' } }, { name: 1, _id: 0 })
db.items.find({}, { price: 1, _id: 0 }).sort({ price: 1 })
Output
{ acknowledged: true, insertedIds: { '0': 1, '1': 2, '2': 3, '3': 4 } }
[ { name: 'Cable' } ]
[ { name: 'Cable' }, { name: 'Case' } ]
[ { name: 'Charger' }, { name: 'Strap' } ]
[ { price: 10 }, { price: 25.5 }, { price: '10' }, { price: '100' } ]
Line-by-line explanation
  1. Line 3This price was saved as text, perhaps from a form or CSV import.
  2. Line 7The number 10 doesn't match the string '10'.
  3. Line 8A numeric comparison ignores the string prices completely.
  4. Line 9$type finds the documents with the wrong type so they can be fixed.
  5. Line 10Numbers sort before strings, and strings sort as text.
Example 2

Exact money with Decimal128

MongoDB Shell
db.payments.insertMany([
  { _id: 1, amount: 0.1, exact: Decimal128('0.10') },
  { _id: 2, amount: 0.2, exact: Decimal128('0.20') }
])
db.payments.aggregate([
  { $group: { _id: null, doubles: { $sum: '$amount' }, decimals: { $sum: '$exact' } } },
  { $project: { _id: 0 } }
])
Output
{ acknowledged: true, insertedIds: { '0': 1, '1': 2 } }
[ { doubles: 0.30000000000000004, decimals: Decimal128('0.30') } ]
Line-by-line explanation
  1. Lines 2 to 3The same amounts are stored as doubles and as Decimal128 values.
  2. Line 6$group adds up every document's values; aggregation is covered in later lessons.
  3. Line 6The double total has a tiny binary rounding error; the decimal total is exact.
Example 3

Dates and converting types

MongoDB Shell
db.events.insertMany([
  { _id: 1, title: 'Launch', at: ISODate('2026-03-01T09:00:00Z'), seats: '120' },
  { _id: 2, title: 'Workshop', at: ISODate('2026-04-15T13:30:00Z'), seats: '40' }
])
db.events.find({ at: { $gte: ISODate('2026-04-01T00:00:00Z') } }, { title: 1, _id: 0 })
db.events.updateMany({ seats: { $type: 'string' } }, [{ $set: { seats: { $toInt: '$seats' } } }])
db.events.find({}, { title: 1, seats: 1, _id: 0 })
db.events.aggregate([{ $project: { _id: 0, title: 1, month: { $month: '$at' }, year: { $year: '$at' } } }])
Output
{ acknowledged: true, insertedIds: { '0': 1, '1': 2 } }
[ { title: 'Workshop' } ]
{
  acknowledged: true,
  insertedId: null,
  matchedCount: 2,
  modifiedCount: 2,
  upsertedCount: 0
}
[ { title: 'Launch', seats: 120 }, { title: 'Workshop', seats: 40 } ]
[
  { title: 'Launch', month: 3, year: 2026 },
  { title: 'Workshop', month: 4, year: 2026 }
]
Line-by-line explanation
  1. Line 5Real dates compare correctly with $gte.
  2. Line 6An update with an aggregation pipeline converts every string seat count into an integer.
  3. Line 8Date operators such as $month and $year read parts of a date, which only works on real Date values.

Common MongoDB Basics Mistakes

Numbers stored as strings

Don't
db.products.insertOne({ name: 'Mug', price: '8.50', stock: '12' })   // can't sum, compare or sort numerically
Do
db.products.insertOne({ name: 'Mug', price: Decimal128('8.50'), stock: 12 })

Money as floating point

Don't
db.accounts.updateOne({ _id: 1 }, { $inc: { balance: 0.1 } })      // errors creep in after many updates
Do
db.accounts.updateOne({ _id: 1 }, { $inc: { balance: Decimal128('0.10') } })

Local times as strings

Don't
db.bookings.insertOne({ when: '1/3/2026 2pm' })                      // ambiguous and can't be compared
Do
db.bookings.insertOne({ when: ISODate('2026-03-01T14:00:00Z'), timezone: 'Europe/London' })

MongoDB Basics in Real Projects

Types in production data

Finance and e-commerce systems store prices and balances as Decimal128 or integer minor units, never as doubles. Analytics events use Date fields so time ranges and grouping by day work, often in time series collections. Data teams regularly run $type queries to find bad records after imports, then fix them with update pipelines using $convert, before adding validation so the problem can't return.

MongoDB Basics FAQ

How do I store money in MongoDB without rounding errors?

Use Decimal128 for amounts, such as Decimal128('19.99'), or store whole minor units as integers, such as 1999 pence. Both keep sums exact, unlike doubles, where 0.1 + 0.2 gives 0.30000000000000004. Pick one approach per field and enforce it with schema validation.

How do I convert a string field to a number in MongoDB?

Use an update with an aggregation pipeline and $convert or $toDouble: db.products.updateMany({ price: { $type: 'string' } }, [{ $set: { price: { $toDouble: '$price' } } }]). Filtering on $type: 'string' leaves correct documents alone. If some strings may not be valid numbers, use $convert with an onError value so one bad record doesn't stop the update.

Does MongoDB store dates in UTC?

Yes. A BSON Date is the number of milliseconds since 1 January 1970 UTC, with no time zone attached. mongosh displays dates in UTC with a Z. Convert to a local time zone only when displaying, or pass a timezone option to date operators such as $dateToString in aggregation.

What is the difference between null and a missing field in MongoDB?

A field set to null exists in the document with an empty value; a missing field isn't there at all. A query for { field: null } matches both. Use { field: { $exists: false } } to find only documents without the field, or { field: { $type: 'null' } } to find only explicit nulls.

How do I check the type of a field in MongoDB?

In a query, use $type: { price: { $type: 'string' } } finds documents where price is a string, and 'number' matches any numeric type. In aggregation, the $type expression returns the type name, for example { $project: { t: { $type: '$price' } } } gives 'double', 'int', 'string' or 'missing'.

MongoDB Basics Exercises

Answer each one and check it instantly. If you get stuck, open the hint before the answer.

Fill in the blankseasy

Complete the value so the price is stored as an exact decimal.

db.products.insertOne({ name: 'Pen', price: ('1.20') })
Multiple choiceeasy

What type does mongosh use for a plain number like 42?

Predict the outputmedium

A collection has prices 5, '7' and 9. How many documents does this match?

db.c.countDocuments({ price: { $gt: 4 } })
Multiple choicemedium

How are dates stored in MongoDB?

Multiple choicehard

Which query finds documents where price is stored as text?

MongoDB Basics Quiz

5 questions to check what you learned.

  1. Which type should store money exactly?

    • Decimal128
    • Double
    • String
    • Boolean
  2. What does BSON add compared with JSON?

    • Comments
    • Extra types such as dates, ObjectIds, integers and Decimal128
    • Functions
    • Nothing
  3. Why does '100' sort before '20' as strings?

    • Strings sort character by character, and '1' comes before '2'
    • It's a bug
    • Numbers always sort first
    • Because of indexes
  4. Which operator converts a field to an integer in an aggregation?

    • $int
    • $parseInt
    • $toInt
    • $number
  5. What does { price: { $type: 'number' } } match?

    • Only doubles
    • Only integers
    • Strings that look like numbers
    • Any numeric type: double, int, long or decimal