MongoDB Data Types and BSON
Master MongoDB's BSON data model, including strings, ObjectId, numeric types, Boolean values, null, missing fields, arrays, embedded documents, dates, timestamps, regular expressions, binary data, Decimal128, type conversion, type checking, and the effects of data types on queries, sorting, validation, and application development.
Understanding BSON and Its Relationship with JSON
MongoDB stores documents using BSON (Binary JSON), a binary-encoded serialization format designed to represent structured data efficiently. BSON builds on a JSON-like document model but supports additional data types and explicit type distinctions.
Understanding BSON is essential because MongoDB does not treat every value as an ordinary JavaScript value or JSON string. The type of a stored value influences matching, sorting, arithmetic, validation, and serialization.
JSON versus BSON
JSON supports objects, arrays, strings, numbers, Booleans, and null. BSON additionally supports types such as ObjectId, Date, Decimal128, binary data, and other specialized values.
Consider these documents:
{
name: "Arun",
age: 28,
active: true
}
{
name: "Arun",
age: NumberInt(28),
active: true,
createdAt: ISODate("2026-10-22T10:00:00Z"),
_id: ObjectId("65a000000000000000000001")
}
The second example uses MongoDB shell notation to demonstrate BSON types. NumberInt(), ISODate(), and ObjectId() are shell helpers, not standard JSON syntax.
Why BSON matters
1. Type-aware queries
MongoDB distinguishes numbers from strings. A field containing the string "28" is not equivalent to a field containing the number 28 in an ordinary equality or numeric range query.
2. Specialized values
Dates should generally be stored as BSON Dates when date comparisons and date arithmetic are required. Monetary values requiring exact decimal representation may benefit from Decimal128 or integer minor units.
3. Document serialization
Applications may transform BSON documents into JSON before returning them through an HTTP API. This transformation can change how values are represented. For example, an ObjectId may become a string, and a BSON Date commonly becomes an ISO-formatted string.
4. Storage and size
BSON has its own encoding rules and overhead. The size of a BSON document is not necessarily the same as the number of characters in its JSON representation.
MongoDB's maximum BSON document size is 16 MiB. This constraint matters when storing large arrays, embedded documents, or binary data.
BSON does not mean every query is binary
Developers generally write queries using MongoDB's document-shaped syntax. The driver serializes supported application values into BSON when communicating with the server.
For example:
db.users.find({ age: { $gte: 18 } });
You write a readable query expression. MongoDB evaluates the filter using its query semantics and stored BSON values.
Practical rule
Choose a type based on the meaning of the data, not merely on how the value looks when printed. A date string may look like a date, but it is not a BSON Date. A numeric string may contain digits, but it is not a BSON number.
Text-based data interchange formatBinary-encoded document representationImportant for query behavior16 MiB- MongoDB uses BSON to represent documents.
- BSON supports types beyond standard JSON.
- The stored BSON type affects query and validation behavior.
- BSON document size is not the same as JSON text length.
- MongoDB documents have a maximum BSON size of 16 MiB.
What is the most important difference between the values `28` and `"28"` in MongoDB?
String Data Type and String Handling
The BSON string type represents textual data such as names, email addresses, titles, descriptions, slugs, and category labels.
Example document:
{
name: "Meera",
email: "meera@example.com",
role: "developer",
country: "India"
}
String values are enclosed in quotation marks in shell examples. MongoDB stores the string content as BSON string data.
Equality matching
db.users.find({ role: "developer" });
This matches documents whose role value equals the string developer, subject to the applicable collation rules.
Case sensitivity
String comparisons are normally case-sensitive under the default simple binary comparison behavior.
For example, these values are different under ordinary simple collation:
"MongoDB"
"mongodb"
"MONGODB"
If your application requires case-insensitive comparisons, consider an appropriate collation or a normalized field, depending on the query pattern and indexing requirements.
String length
A string can contain a single character or a long description. MongoDB supports string operators in queries and aggregation expressions, including operations for transforming or examining string content.
For example, an aggregation can calculate the string length of a title:
db.courses.aggregate([
{
$project: {
title: 1,
titleLength: { $strLenCP: "$title" }
}
}
]);
$strLenCP counts Unicode code points rather than UTF-8 bytes. This distinction matters when strings contain non-ASCII characters.
Normalization considerations
A user may enter Arun, arun, or ARUN. Whether those values should be considered equivalent depends on the application's requirements.
For usernames or email lookup, define a consistent normalization strategy. A normalized field can simplify lookups and indexing, but normalization rules should be applied consistently during both writes and queries.
Do not blindly lowercase every string. Names, passwords, tokens, identifiers, and natural-language text have different semantics.
Strings that contain numbers
Avoid storing numeric data as strings when numeric comparisons or arithmetic are required.
For example:
{ price: "100" }
is not equivalent to:
{ price: 100 }
Use strings for identifiers whose meaning is textual, such as postal codes or phone numbers, even if they contain only digits. Use numeric types when the value is genuinely a quantity.
db.stringExamples.insertMany([
{ name: "Arun", role: "developer" },
{ name: "Meera", role: "Developer" },
{ name: "Ravi", role: "admin" }
]);
// Exact string match under the collection's applicable collation.
db.stringExamples.find({ role: "developer" });
// Inspect string lengths using aggregation.
db.stringExamples.aggregate([
{
$project: {
name: 1,
role: 1,
roleLength: { $strLenCP: "$role" }
}
}
]);developer and Developer do not match each other. $strLenCP counts Unicode code points and is useful when measuring text length without counting UTF-8 bytes.- Strings represent textual values.
- Default string matching is case-sensitive under simple collation.
- Collation can change string comparison behavior.
- String normalization should follow the meaning of the field.
- Store identifiers as strings when they represent labels rather than quantities.
Under simple collation, what happens when querying `{ role: "developer" }` against a document containing `{ role: "Developer" }`?
ObjectId: Structure, Generation, and Querying
ObjectId is a BSON type commonly used for MongoDB document identifiers. When an inserted document does not specify _id, MongoDB drivers commonly generate an ObjectId automatically unless the collection or insertion workflow uses another supported identifier strategy.
Example:
{
_id: ObjectId("65a000000000000000000001"),
name: "Arun"
}
Why _id exists
Each document in a collection must have a unique _id value. MongoDB creates a unique _id index for ordinary collections to enforce this requirement.
ObjectId is the conventional default, but _id can use other supported BSON types, including strings, numbers, and UUID representations. Your choice should reflect the application's requirements.
ObjectId structure
A standard ObjectId is 12 bytes, conventionally represented as 24 hexadecimal characters.
Its components are:
- A 4-byte timestamp representing seconds since the Unix epoch.
- A 5-byte process-unique random value.
- A 3-byte counter.
This structure supports distributed generation with a low probability of collisions. It should not be treated as a mathematically absolute guarantee that collisions are impossible.
Generating an ObjectId
In mongosh:
const id = ObjectId();
print(id);
In a Node.js application using the MongoDB driver:
import { ObjectId } from "mongodb";
const id = new ObjectId();
console.log(id.toHexString());
The exact import path depends on the package and module system used by your project.
Querying by ObjectId
const id = ObjectId("65a000000000000000000001");
db.users.findOne({ _id: id });
If _id is stored as an ObjectId, querying it with the corresponding hexadecimal string does not ordinarily match the stored ObjectId.
This distinction is common when handling route parameters in a web application. An API may receive an ID as a string, but the backend must convert it to an ObjectId when the database field uses that BSON type.
ObjectId timestamp extraction
In mongosh, an ObjectId provides access to its embedded timestamp:
const id = ObjectId();
id.getTimestamp();
The timestamp has only second-level resolution. It is not a reliable substitute for a dedicated createdAt field when an application requires precise event times or explicit business semantics.
ObjectId is not an authorization mechanism
An ObjectId can be difficult to guess in many contexts, but its format does not provide authorization. Every endpoint that accesses a resource should verify that the authenticated caller has permission to access that resource.
db.objectIdExamples.insertOne({
name: "Arun",
createdAt: new Date()
});
const document = db.objectIdExamples.findOne({ name: "Arun" });
const id = document._id;
print("ObjectId:", id);
print("Hex string:", id.toHexString());
print("Timestamp:", id.getTimestamp());
// Query using the BSON ObjectId.
db.objectIdExamples.findOne({ _id: id });
// Convert a known hexadecimal string into an ObjectId.
db.objectIdExamples.findOne({
_id: ObjectId(id.toHexString())
});_id, retrieves the generated identifier, and inspects its hexadecimal representation and embedded timestamp. ObjectId values and their string representations are different types. The timestamp embedded in ObjectId has second-level resolution and should not replace a dedicated creation timestamp when greater precision is needed.- ObjectId is a BSON type commonly used for document identifiers.
- A standard ObjectId contains 12 bytes and is represented by 24 hexadecimal characters.
- MongoDB requires a unique
_idvalue for each document in a collection. - A hexadecimal string must be converted to ObjectId when querying an ObjectId field.
- ObjectId is not a replacement for authorization or a precise business timestamp.
A document stores `_id` as an ObjectId. Why might `{ _id: "65a000000000000000000001" }` fail to find it?
Numeric BSON Types: Int32, Int64, Double, and Decimal128
MongoDB supports multiple numeric BSON types. Choosing the appropriate type matters for precision, range, arithmetic, validation, and interoperability with application languages.
The main numeric BSON types are:
- Int32: A signed 32-bit integer.
- Int64 (Long): A signed 64-bit integer.
- Double: A 64-bit IEEE 754 floating-point number.
- Decimal128: A decimal floating-point type designed for decimal precision and a wide numeric range.
MongoDB also has BSON-specific numeric representations and shell helpers for creating particular types.
Int32
Int32 is suitable for integers within the signed 32-bit range:
- Minimum: -2,147,483,648
- Maximum: 2,147,483,647
In mongosh, NumberInt() explicitly creates an Int32 value:
{ age: NumberInt(28) }
The standard JavaScript number type does not separately represent Int32 values, so the shell helper is useful when you need to demonstrate or create that BSON type explicitly.
Int64 (Long)
Int64 supports larger signed integers:
- Minimum: -9,223,372,036,854,775,808
- Maximum: 9,223,372,036,854,775,807
In mongosh:
{ totalViews: NumberLong("9000000000") }
The string form avoids accidentally passing an already-imprecise JavaScript number when constructing an integer outside JavaScript's safe integer range.
JavaScript's largest safe integer is 9007199254740991. A JavaScript number cannot represent every integer above that value exactly. If your application must handle large integers, use a driver-supported representation that preserves the intended BSON Int64 value.
Double
Double is a binary floating-point type. It supports a wide range of numeric values, but many decimal fractions cannot be represented exactly in binary floating-point.
For example, calculations involving 0.1 and 0.2 can produce representation effects in floating-point arithmetic.
Double is often appropriate for measurements, scientific values, and approximate numerical computations.
Decimal128
Decimal128 is designed for decimal floating-point values and is often appropriate for monetary calculations that require decimal precision.
In mongosh:
{
price: NumberDecimal("19.99")
}
Construct Decimal128 from a decimal string when exact decimal input matters. Converting an already-rounded binary floating-point value into Decimal128 cannot recover the decimal digits that were lost earlier.
Currency modeling
Two common strategies are:
Strategy A: Decimal128
{
price: NumberDecimal("1999.95"),
currency: "INR"
}
Strategy B: Integer minor units
{
pricePaise: NumberLong("199995"),
currency: "INR"
}
The integer-minor-unit approach can work well when the currency and its scale are clearly defined. Not every currency uses two decimal places, so do not assume that every currency can be represented as cents or paise using a universal factor of 100.
Numeric type conversion
Aggregation provides conversion operators such as $convert, $toInt, $toLong, $toDouble, and $toDecimal. Conversion may fail when a value cannot be represented in the requested type.
For inconsistent historical data, $convert can specify onError and onNull values to control conversion behavior.
Do not assume that converting every field to Double is safe. A conversion can lose precision, overflow the target type, or fail for invalid input.
Signed 32-bit integerSigned 64-bit integerBinary floating-pointDecimal floating-pointBinary floating-point cannot represent every decimal fraction exactly. Consider Decimal128 or integer minor units with a well-defined currency scale when financial correctness matters.
db.numericExamples.insertOne({
age: NumberInt(28),
totalViews: NumberLong("9000000000"),
temperature: 36.6,
price: NumberDecimal("1999.95")
});
// Inspect the stored numeric values.
db.numericExamples.findOne();
// Calculate a converted decimal value in aggregation.
db.numericExamples.aggregate([
{
$project: {
totalViews: 1,
price: 1,
priceAsDouble: { $toDouble: "$price" }
}
}
]);$toDouble converts the decimal price to Double for demonstration, but this conversion may lose decimal precision. Avoid converting monetary values to Double when exact decimal arithmetic is required.- MongoDB supports Int32, Int64, Double, and Decimal128.
- JavaScript numbers cannot represent every integer above the safe integer limit exactly.
- Double is appropriate for many approximate numerical calculations.
- Decimal128 is useful when decimal precision is important.
- Converting numeric types can introduce errors or fail for out-of-range values.
Which BSON numeric type is generally a strong candidate for storing decimal monetary values?
Boolean Values and Boolean Query Semantics
The BSON Boolean type represents either true or false.
Typical fields include:
isActivepublishedemailVerifiedisDeletedhasAccess
Example:
{
name: "Meera",
emailVerified: true,
isActive: false
}
Exact Boolean matching
db.users.find({ emailVerified: true });
This matches documents where emailVerified equals the Boolean value true.
The following value is not the same BSON type:
{ emailVerified: "true" }
The string "true" is text, not a Boolean.
Missing fields and Booleans
Consider these documents:
{ name: "Arun", isActive: true }
{ name: "Meera", isActive: false }
{ name: "Ravi" }
The third document has no isActive field. A query for { isActive: false } does not generally mean 'the field is false or absent'; it matches the explicit Boolean false value.
If you need to match missing fields, use $exists:
db.users.find({ isActive: { $exists: false } });
Boolean fields versus status fields
A Boolean is appropriate when the concept genuinely has two states. If a workflow has states such as draft, published, archived, and rejected, a string or another controlled representation may be more expressive than multiple Boolean flags.
For example, separate flags such as isDraft, isPublished, and isArchived can permit contradictory combinations unless the application or database enforces consistency.
A status field can model the mutually exclusive state directly:
{ status: "published" }
Use a schema validation rule or application-level validation when only a defined set of statuses is permitted.
db.booleanExamples.insertMany([
{ name: "Arun", isActive: true },
{ name: "Meera", isActive: false },
{ name: "Ravi" },
{ name: "Kavitha", isActive: "false" }
]);
// Explicit Boolean false.
db.booleanExamples.find({ isActive: false });
// Field is absent.
db.booleanExamples.find({ isActive: { $exists: false } });
// Field is stored as a string.
db.booleanExamples.find({ isActive: { $type: "string" } });false. These are distinct cases. The equality filter for Boolean false does not treat the string "false" as Boolean false and does not generally include documents where the field is absent.- Boolean values are
trueandfalse. - Strings such as
"true"and"false"are not Boolean values. - An absent Boolean field differs from an explicit false value.
- Use a status field when a concept has several mutually exclusive states.
- Validation can prevent accidental Boolean type inconsistencies.
Which query specifically finds documents where the `isActive` field is absent?
Null, Missing Fields, and Undefined Values
One of the most important MongoDB distinctions is the difference between a field that contains null and a field that does not exist.
Consider the following documents:
{ name: "Arun", phone: null }
{ name: "Meera" }
{ name: "Ravi", phone: "9876543210" }
These represent three different situations:
- The phone field exists and its value is null.
- The phone field is absent.
- The phone field contains a string.
The distinction affects query results, aggregation, validation, and application logic.
Querying with null
db.users.find({ phone: null });
This query matches documents where phone is explicitly null and documents where phone is missing.
This behavior is useful to know, but it may be too broad when the application needs only one of those states.
Finding explicit null values
db.users.find({
phone: { $type: "null" }
});
This identifies documents where phone contains the BSON null type.
Finding missing fields
db.users.find({
phone: { $exists: false }
});
This identifies documents that do not contain the phone field.
Finding fields that exist and are not null
db.users.find({
phone: {
$exists: true,
$ne: null
}
});
This commonly selects documents where phone exists and is not null. It does not guarantee that the value is a valid phone number; type and format validation may still be needed.
Why missing fields occur
Missing fields can result from optional attributes, legacy document versions, incomplete migrations, conditional application writes, or inconsistent data contracts.
For example, an older user record may not contain emailVerified because the application did not introduce that field until a later version.
Null is not a universal substitute for missing
Use null when the field exists but has no known or applicable value, according to your data contract. Omit a field when its absence has a meaningful interpretation or when it is optional.
The correct choice depends on your application's semantics. Do not use null and missing interchangeably without considering how queries and validation interpret them.
Undefined values
JavaScript has an undefined value, but BSON's supported value model is not the same as JavaScript's. Do not design a MongoDB schema around storing undefined as though it were a normal, portable BSON value.
Modern MongoDB applications should use explicit values, null where appropriate, or omitted fields according to their data model. Legacy data may contain historical representations that need separate inspection and migration.
db.nullExamples.insertMany([
{ name: "Arun", phone: null },
{ name: "Meera" },
{ name: "Ravi", phone: "9876543210" }
]);
// Null or missing.
db.nullExamples.find({ phone: null });
// Explicit BSON null.
db.nullExamples.find({ phone: { $type: "null" } });
// Missing field.
db.nullExamples.find({ phone: { $exists: false } });
// Existing and non-null.
db.nullExamples.find({
phone: { $exists: true, $ne: null }
});$type: "null" filter isolates explicit null values, while $exists: false identifies missing fields. The final query selects documents where the field exists and is not null, though it does not validate the phone number's format.- Null and missing fields represent different data states.
- A query using
{ field: null }matches explicit null and missing fields. $type: "null"identifies explicit BSON null values.$exists: falseidentifies missing fields.- Schema design should define when to use null and when to omit a field.
Which query matches documents where `phone` is explicitly stored as BSON null rather than being absent?
Array Data Type and Nested Array Values
A BSON array stores an ordered sequence of values. Arrays are useful for tags, skills, prerequisites, addresses, line items, and other related collections of values.
Example:
{
name: "Arun",
skills: ["JavaScript", "Node.js", "MongoDB"]
}
Arrays of scalar values
An array can contain strings, numbers, Booleans, dates, or other supported BSON values.
{
title: "MongoDB Fundamentals",
tags: ["database", "backend", "mongodb"]
}
A query can match a document by an array element:
db.courses.find({ tags: "mongodb" });
This finds documents whose tags array contains the string mongodb.
Matching multiple elements
db.courses.find({
tags: { $all: ["mongodb", "backend"] }
});
$all requires the array to contain all specified values. The elements do not need to be adjacent.
Exact array matching
db.courses.find({
tags: ["mongodb", "backend"]
});
This is not the same as searching for documents that contain both values anywhere in the array. Exact array equality considers the array's contents and order.
Arrays of embedded documents
Arrays can contain documents:
{
title: "MongoDB Mastery",
lessons: [
{ title: "Introduction", durationMinutes: 20 },
{ title: "Queries", durationMinutes: 45 }
]
}
You can query nested fields inside arrays, but conditions involving multiple properties of the same array element require careful handling. The $elemMatch operator is often useful for that purpose.
Unbounded array risks
An array that grows indefinitely can lead to oversized documents, increasingly expensive updates, and inefficient queries. MongoDB has a maximum BSON document size of 16 MiB.
For example, storing every historical event for a busy user inside a single user document is often a poor design. A separate event collection may be more appropriate.
Array order and duplicate values
Arrays preserve element order and can contain duplicate values. Whether duplicates are allowed should be determined by the application requirements and validation strategy.
Do not assume that a MongoDB array automatically behaves like a mathematical set.
db.arrayExamples.insertMany([
{
title: "MongoDB Basics",
tags: ["mongodb", "backend"],
lessons: [
{ title: "Introduction", durationMinutes: 20 },
{ title: "Queries", durationMinutes: 45 }
]
},
{
title: "React Basics",
tags: ["react", "frontend"],
lessons: [
{ title: "Components", durationMinutes: 30 }
]
}
]);
// Match one array element.
db.arrayExamples.find({ tags: "mongodb" });
// Require both values somewhere in the array.
db.arrayExamples.find({
tags: { $all: ["mongodb", "backend"] }
});
// Find an array element with a matching embedded field.
db.arrayExamples.find({
"lessons.title": "Queries"
});$all requires all specified values in the array. Dot notation can query nested fields within arrays of documents.- Arrays preserve order and can contain duplicate values.
- Queries can match array elements directly.
$allrequires the array to contain all specified values.- Exact array equality differs from element matching.
- Unbounded arrays can create document-size and performance problems.
What does `{ tags: { $all: ["mongodb", "backend"] } }` require?
Embedded Documents and Nested BSON Structures
An embedded document is a document stored as the value of a field inside another document.
Example:
{
name: "Meera",
address: {
street: "Example Street",
city: "Chennai",
country: "India"
}
}
The address field contains another document with its own fields.
Querying nested fields
Use dot notation to match a nested field:
db.users.find({ "address.city": "Chennai" });
Nested field projection
db.users.find(
{},
{ _id: 0, name: 1, "address.city": 1 }
);
This projection returns the name and the nested city field while excluding _id.
Embedded document equality
MongoDB can compare an embedded document as a value, but exact embedded-document equality is stricter than checking a nested field. Field values and field order can matter in embedded-document equality comparisons.
For ordinary application queries, dot notation is often preferable when you want to match a particular nested property.
Embedded documents versus references
Embedding is useful when related information is commonly read and updated together and has an appropriate bounded size.
References are often useful when related records:
- Have independent lifecycles.
- Are shared by many parent records.
- Grow without a practical bound.
- Need to be queried independently.
For example, an address embedded in a user profile may be appropriate. A large history of transactions is generally better modeled separately.
Deep nesting
MongoDB supports nested documents, but extremely deep or complicated structures can make queries and application code difficult to maintain. The design should represent the domain clearly rather than maximizing nesting depth.
Field names containing dots or dollar signs
Dot notation uses dots to address nested paths, so ordinary application field names should avoid ambiguity with path syntax. MongoDB has restrictions and special handling for field names containing periods or beginning with dollar signs, with limitations across operations and tools. Prefer conventional field names unless a specific requirement justifies an unusual name.
db.embeddedExamples.insertOne({
name: "Meera",
address: {
city: "Chennai",
country: "India"
},
preferences: {
theme: "dark",
notifications: {
email: true,
sms: false
}
}
});
// Query a nested city.
db.embeddedExamples.find({ "address.city": "Chennai" });
// Query a deeply nested Boolean.
db.embeddedExamples.find({
"preferences.notifications.email": true
});
// Project selected nested fields.
db.embeddedExamples.find(
{},
{ _id: 0, name: 1, "address.city": 1 }
);- Embedded documents represent nested structures within a parent document.
- Dot notation queries nested fields.
- Exact embedded-document equality is stricter than matching a nested property.
- Embedding and referencing should be selected according to access patterns and growth.
- Unnecessarily deep nesting can make data models harder to maintain.
Which query selects users whose nested address city is Chennai?
Date Type: UTC, Time Zones, and Date Queries
MongoDB's BSON Date type represents a point in time as a signed 64-bit integer containing milliseconds since the Unix epoch. It does not store an arbitrary time-zone name as part of the Date value.
For example:
{
createdAt: ISODate("2026-10-22T10:30:00Z")
}
The Z suffix indicates UTC.
BSON Date versus a string
These documents contain different types:
{ createdAt: ISODate("2026-10-22T10:30:00Z") }
{ createdAt: "2026-10-22T10:30:00Z" }
The first contains a BSON Date. The second contains a string. Store dates as BSON Dates when chronological comparisons and date arithmetic are required.
Creating dates in mongosh
new Date()
This creates a JavaScript Date representing the current instant. When inserted through the MongoDB shell, it is stored as a BSON Date.
A fixed date can be created using:
new Date("2026-10-22T10:30:00Z")
Filtering by a date range
db.events.find({
createdAt: {
$gte: ISODate("2026-10-01T00:00:00Z"),
$lt: ISODate("2026-11-01T00:00:00Z")
}
});
This selects events in October 2026 using an inclusive lower boundary and exclusive upper boundary.
Half-open ranges are useful because consecutive intervals can be joined without overlapping at the boundary.
Time zones
A Date represents an instant, not a local calendar label. A local time such as 9:00 a.m. in Asia/Kolkata should be converted to the corresponding UTC instant before being stored if it represents a specific moment.
For example, India Standard Time is UTC+05:30. A local time of 2026-10-22 09:00 in that time zone corresponds to 2026-10-22T03:30:00Z.
When working with user schedules, recurring events, or appointments, consider whether the application needs to retain the user's time-zone identifier in addition to the stored instant.
Date range filtering by local day
To query all records for a particular local calendar day, calculate the UTC start of that local day and the UTC start of the next local day using the intended time zone. Query with $gte and $lt using those two instants.
Do not assume that every local day is exactly 24 hours in every time zone; daylight-saving transitions can produce shorter or longer local days.
Date arithmetic
MongoDB's aggregation framework provides date operators for extracting components, formatting dates, calculating differences, and performing date arithmetic. These operations are covered in greater detail in the aggregation chapters.
ISO-formatted strings may sort lexicographically in useful ways under restricted conditions, but they are not equivalent to BSON Dates and do not provide the same date-query semantics.
db.dateExamples.insertMany([
{
event: "A",
createdAt: ISODate("2026-10-01T00:00:00Z")
},
{
event: "B",
createdAt: ISODate("2026-10-15T12:30:00Z")
},
{
event: "C",
createdAt: ISODate("2026-11-01T00:00:00Z")
}
]);
// October 2026 only.
db.dateExamples.find({
createdAt: {
$gte: ISODate("2026-10-01T00:00:00Z"),
$lt: ISODate("2026-11-01T00:00:00Z")
}
});- BSON Date stores a point in time with millisecond precision.
- A BSON Date does not retain an arbitrary time-zone identifier.
- Use UTC instants consistently for date comparisons.
- Half-open date ranges avoid overlapping consecutive periods.
- Convert local-day boundaries using the intended time zone.
What does a BSON Date primarily represent?
BSON Timestamp versus BSON Date
MongoDB has a BSON Timestamp type that is distinct from BSON Date. This distinction is important because application developers sometimes assume that both types serve the same purpose.
BSON Date
BSON Date is designed for application date-and-time values such as:
createdAtupdatedAtpublishedAtexpiresAtscheduledAt
Example:
{ createdAt: ISODate("2026-10-22T10:30:00Z") }
BSON Timestamp
BSON Timestamp is a special internal type consisting of a seconds component and an ordinal component. MongoDB uses it in internal contexts, including replication-related metadata and the oplog.
It is not generally the preferred type for ordinary application timestamps.
In mongosh, a Timestamp can be constructed using the shell helper:
Timestamp(10, 1)
This illustrates the type's two components; it is not an application timestamp such as October 22, 2026.
Why the distinction matters
A field called createdAt normally represents a real-world instant. BSON Date expresses that meaning directly and supports date operators and date comparisons.
BSON Timestamp is designed for specialized database uses and should not be selected simply because the field name contains the word timestamp.
Avoid relying on internal timestamps as business data
Do not treat an internal replication timestamp as a universal creation time for application documents. If your application needs an auditable creation time, store an explicit BSON Date field and define how it is populated and updated.
Application time valuesSpecialized internal typeExplicit Date fieldcreatedAt when the application requires an auditable creation time.- BSON Timestamp and BSON Date are different BSON types.
- BSON Date is the usual choice for application-level time values.
- BSON Timestamp has specialized database uses.
- A field's name does not determine its BSON type.
- Store explicit application timestamps when business logic depends on them.
Which type should normally be used for an application's `createdAt` field?