Automattic/mongoose · error · CastError
Cast to ObjectId failed for value "${value}" (type ${valueTy
Error message
Cast to ObjectId failed for value "${value}" (type ${valueType}) at path "${path}" What it means
SchemaObjectId.cast wraps the ObjectId caster (lib/cast/objectid.js), which passes through real ObjectIds, plucks `value._id` when present, and otherwise calls `new ObjectId(value.toString())`. That bson constructor throws for any string that is not 24 hex characters (or 12 bytes), and mongoose wraps the failure as CastError 'ObjectId' — the wrapped form of bson's 'Argument passed in must be a single String of 12 bytes or a string of 24 hex characters'.
Source
Thrown at lib/schema/objectId.js:252
if (value == null || utils.isNonBuiltinObject(value)) {
return this._castRef(value, doc, init, options);
}
}
let castObjectId;
if (typeof this._castFunction === 'function') {
castObjectId = this._castFunction;
} else if (typeof this.constructor.cast === 'function') {
castObjectId = this.constructor.cast();
} else {
castObjectId = SchemaObjectId.cast();
}
try {
return castObjectId(value);
} catch (error) {
throw new CastError('ObjectId', value, this.path, error, this);
}
};
/*!
* ignore
*/
function handleSingle(val) {
return this.cast(val);
}
const $conditionalHandlers = {
...SchemaType.prototype.$conditionalHandlers,
$gt: handleSingle,
$gte: handleSingle,
$lt: handleSingle,
$lte: handleSingle
};View on GitHub (pinned to 49cdab0136)
Solutions
- Validate first: `if (!mongoose.isValidObjectId(id)) return 400` — check shape before touching the model
- Convert explicitly at the boundary inside try/catch: `new mongoose.Types.ObjectId(id)` and reject on throw
- If you accept non-ObjectIds (UUIDs etc.), change the path type to String
- In bulk $in filters, filter the array first: `ids.filter(mongoose.isValidObjectId)`
Example fix
// before
const user = await User.findById(req.params.id); // 'abc' → CastError
// after
if (!mongoose.isValidObjectId(req.params.id)) {
return res.status(400).json({ error: 'invalid id' });
}
const user = await User.findById(req.params.id); Defensive patterns
Strategy: type-guard
Validate before calling
if (!mongoose.isValidObjectId(id)) {
throw Object.assign(new Error('invalid id'), { status: 400 });
}
const doc = await Model.findById(id); Type guard
function isObjectIdLike(v) {
return v == null || v instanceof mongoose.Types.ObjectId ||
(typeof v === 'string' && /^[a-f0-9]{24}$/i.test(v));
} Try / catch
try { doc.owner = id; } catch (err) { if (err.name === 'CastError' && err.kind === 'ObjectId') { return badRequest(`invalid ObjectId for ${err.path}`); } throw err; } Prevention
- Validate route params with mongoose.isValidObjectId before querying
- Filter $in arrays: ids.filter(mongoose.isValidObjectId)
- Use String paths for UUID/ULID identifiers instead of ObjectId
When it happens
Trigger: `doc.owner = 'abc'` (wrong length), `doc._id = '507f1f77bcf86cd7994d90'` (23 chars), `{ owner: { $in: ['xyz', ...] } }`, assigning UUIDs or arbitrary slugs to an ObjectId path, or an object whose toString() yields a non-hex string.
Common situations: Route params (`/users/:id`) receiving garbage or empty strings without validation; ids from external systems (UUID, ULID, ints) assigned to ObjectId fields; truncated ids from logs; case-sensitive hex issues (uppercase hex actually works — length is the usual culprit); populated docs re-serialized to strings with quotes.
Related errors
- Query filter must be an object, got an array ${util.inspect(
- Cast to date failed for value "${value}" (type ${valueType})
- Cast to Decimal128 failed for value "${value}" (type ${value
- Cast to Double failed for value "${value}" (type ${valueType
- Cast to Int32 failed for value "${value}" (type ${valueType}
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/db6241169df6d1d7.
Report an issue: GitHub.