What it means
An ObjectId is 12 bytes, written as 24 hexadecimal characters (0–9, a–f), for example
65f1c2e9a1b2c3d4e5f6a7b8. new ObjectId(value) (and ObjectId(value) in mongosh) builds one
from a 24-character hex string, 12 raw bytes, or an integer. Given a string that isn’t 24 hex
characters, the bson library that mongosh and the Node.js driver use throws BSONError. It happens
in your code, before a query is sent, so the server never sees it.
Older versions of the library said it differently, as a BSONTypeError:
Argument passed in must be a string of 12 bytes or a string of 24 hex characters. It’s the same
problem. ObjectId.createFromHexString() words it as hex string must be 24 characters. Mongoose
wraps the same failure in Cast to ObjectId failed.
Common causes
- A value from a URL or form that isn’t an id:
undefined,me,new, a slug, or the literal:id, passed straight tonew ObjectId(req.params.id). - A truncated or padded id: 23 or 25 characters, or a space or newline at either end.
- Characters that aren’t hex: an
oinstead of a0, or an id copied with its quotes or with theObjectId('…')around it. - Ids that aren’t ObjectIds at all. The collection’s
_ids are strings or numbers, and the code converts every id it gets.
How to fix it
Check the value before converting it
const { ObjectId } = require("mongodb")
app.get("/orders/:id", async (req, res) => {
const id = req.params.id.trim()
if (!/^[0-9a-f]{24}$/i.test(id)) {
return res.status(400).json({ error: "Invalid order id" })
}
const order = await db.collection("orders").findOne({ _id: new ObjectId(id) })
if (!order) return res.status(404).end()
res.json(order)
})
The pattern accepts exactly 24 hex digits, in either case. ObjectId.isValid(id) does much the
same for strings, but it also returns true for a number, so it says less about a value whose type
you don’t know.
Find the bad value
Print it with JSON.stringify so spaces and newlines show, and check its length. If it came from a
URL, check the code that built the URL: /orders/undefined means a variable wasn’t set.
Keep string ids as strings
If the collection’s _ids aren’t ObjectIds, don’t convert them. Ask the server which types it
holds:
db.orders.aggregate([{ $group: { _id: { $type: "$_id" }, n: { $sum: 1 } } }])
Then query with the type you get. A string never matches an ObjectId, even with the same hex
digits: find({ _id: "65f1c2e9a1b2c3d4e5f6a7b8" }) finds nothing when the stored _id is
ObjectId("65f1c2e9a1b2c3d4e5f6a7b8").
Watch for ObjectId(undefined)
new ObjectId(undefined) doesn’t throw: it makes a brand-new id, so a query with it quietly matches
nothing. If the value can be missing, check for that before converting.
Reproduce it
mongosh 2.12.0, connected to MongoDB 8.0.32:
ObjectId("abc")
BSONError: input must be a 24 character hex string, 12 byte Uint8Array, or an integer
A 23-character id and an id with a trailing space failed the same way; after .trim() the second
worked. ObjectId.createFromHexString("abc") gave BSONError: hex string must be 24 characters.
ObjectId.isValid returned true for the 24-character id and false for "hello" and for a
12-character string.
From a Node.js script with the driver 6.20.0 (bson 6.10.4), new ObjectId(v) threw the same
BSONError for "abc", a 23-character id, a padded id, "zzzzzzzzzzzzzzzzzzzzzzzz" and
"aaaaaaaaaaaa". An upper-case hex id was accepted and printed in lower case. undefined and
null each returned a new id, and 123 an id built from the number. In a collection where a
document’s _id was an ObjectId, findOne with the same id as a string returned null.
In Inlet
Queries use mongosh syntax, so db.orders.find({ _id: ObjectId("65f1c2e9a1b2c3d4e5f6a7b8") }) works
as it does in mongosh. Collections show
as tables with each document in the inspector, so you can see whether an _id is an ObjectId or a
string before you query it.