Download

Cast to ObjectId failed for value

Mongoose tried to turn a value into an ObjectId and couldn’t, because it isn’t 24 hex characters. Usually a route parameter such as “undefined”, “me” or a mistyped id; check it with isValidObjectId and answer 400 or 404.

MongoDB error· Tested on Mongoose 8.23.0 (Node.js driver 6.16.0), MongoDB 8.0.32· Updated 11 October 2026

Cast to ObjectId failed for value "abc" (type string) at path "_id" for model "User"

What it means

An ObjectId is MongoDB’s default _id: 12 bytes, written as 24 hexadecimal characters such as 65f1c2e9a1b2c3d4e5f6a7b8. When a schema path is an ObjectId (_id always is unless you declare it otherwise, and so is every ref field), Mongoose converts whatever you pass into one before it builds the query or saves the document. Mongoose calls that casting. If the value can’t become an ObjectId, Mongoose throws a CastError and nothing is sent to MongoDB.

The message names the three things you need:

  • for value "abc" (type string): the value it got, and its JavaScript type.
  • at path "_id": the field.
  • for model "User": the model you queried.

The error’s reason holds the underlying failure from the bson library, input must be a 24 character hex string. When you save a document instead of querying, the CastError is wrapped in a ValidationError: Order validation failed: user: Cast to ObjectId failed for value … because of "BSONError".

Common causes

  1. A route parameter that isn’t an id. /users/:id receives me, new, the literal :id, or undefined from a front end that built the URL from a variable that wasn’t set yet.
  2. Route order. In Express, /users/:id declared before /users/new catches new as an id.
  3. A truncated or padded id: 23 characters copied instead of 24, or a leading or trailing space or newline.
  4. The wrong value in a reference field: an email address, a name or a whole object where the schema expects the referenced document’s _id.
  5. Ids that aren’t ObjectIds. The collection uses string or numeric _ids (slugs, UUIDs, imported keys) but the schema still has the default ObjectId _id.

How to fix it

Find the value and where it came from

Log the error’s fields rather than only its message:

catch (err) {
  if (err.name === "CastError") console.log(err.path, err.kind, JSON.stringify(err.value))
}

JSON.stringify shows spaces and newlines that a plain log hides. Then trace the value back to the request, form or variable that produced it.

Check the id before you query

Answer a bad id with 400 and a missing document with 404, instead of letting the CastError become a 500:

const mongoose = require("mongoose")

app.get("/users/:id", async (req, res) => {
  if (!mongoose.isObjectIdOrHexString(req.params.id)) {
    return res.status(400).json({ error: "Invalid user id" })
  }
  const user = await User.findById(req.params.id)
  if (!user) return res.status(404).end()
  res.json(user)
})

isObjectIdOrHexString accepts only an ObjectId or a 24-character hex string. mongoose.isValidObjectId is looser: it also returns true for a number such as 123, because an ObjectId can be built from an integer.

Put fixed routes before parameter routes

app.get("/users/new", showNewUserForm)
app.get("/users/me", showCurrentUser)
app.get("/users/:id", showUser)

Store the referenced document’s _id

Look the document up first, then store its _id:

const user = await User.findOne({ email: "ada@example.com" })
await Order.create({ user: user._id, total: 5 })

Declare the _id you really use

If your ids are strings or numbers, say so in the schema, and Mongoose stops casting them:

const Page = mongoose.model("Page", new mongoose.Schema({ _id: String, title: String }))
await Page.findById("about-us")

To see which type the stored ids have, ask the server: db.pages.aggregate([{ $group: { _id: { $type: "$_id" }, n: { $sum: 1 } } }]).

Handle it once, in your error middleware

app.use((err, req, res, next) => {
  if (err.name === "CastError") return res.status(400).json({ error: `Invalid ${err.path}` })
  next(err)
})

Reproduce it

Mongoose 8.23.0 (with the Node.js driver 6.16.0) against MongoDB 8.0.32, with a User model and an Order model whose user field is an ObjectId reference:

await User.findById("abc")
CastError: Cast to ObjectId failed for value "abc" (type string) at path "_id" for model "User"

Its reason was BSONError: input must be a 24 character hex string, 12 byte Uint8Array, or an integer, kind was ObjectId and path _id. The values "undefined", "me", ":id", a 23-character id and an id with a leading space all failed the same way, as did User.findOne({ _id: "new" }). An object failed as (type Object):

CastError: Cast to ObjectId failed for value "{ name: 'x' }" (type Object) at path "_id" for model "User"

Saving an email address into the reference field:

await Order.create({ user: "ada@example.com", total: 5 })
ValidationError: Order validation failed: user: Cast to ObjectId failed for value "ada@example.com" (type string) at path "user" because of "BSONError"

Order.find({ user: { $in: ["x", user._id] } }) failed at path user. A well-formed id that matches no document returned null without an error, and so did findById(undefined). isValidObjectId returned false for "abc" and for a 12-character string, and true for 123; isObjectIdOrHexString(123) returned false. With _id: String in the schema, findById("about-us") found its document.

In Inlet

Inlet shows MongoDB collections as tables and each document in the inspector, so you can see whether _id and reference fields hold ObjectIds or strings. Queries use mongosh syntax, so db.orders.find({ user: ObjectId("65f1c2e9a1b2c3d4e5f6a7b8") }) checks what a reference points at.

Inlet: a database client for the Mac

One native app for PostgreSQL, MySQL, SQL Server, SQLite, MongoDB and Redis. It explains errors where they happen, holds your edits until you save them, and keeps production read-only until you say so.

Version 0.1.0 · macOS 26 Tahoe or later · Apple silicon and Intel