Automattic/mongoose · error · CastError
Cast to DocumentArray failed for value "${value}" (type ${va
Error message
Cast to DocumentArray failed for value "${value}" (type ${valueType}) at path "${path}" What it means
CastError thrown when a non-array value is assigned to a subdocument-array path while `castNonArrays` is disabled. In this codebase the global default is lenient (`SchemaDocumentArray.options = { castNonArrays: true }` wraps the value into an array), so this error means castNonArrays was explicitly turned off — per path (`{ type: [subSchema], castNonArrays: false }`) or globally — and then an object or scalar was assigned. During hydration from the database (init) the value is always wrapped, so the throw only happens on user-side assignment and query casting.
Source
Thrown at lib/schema/documentArray.js:400
SchemaDocumentArray.prototype.cast = function(value, doc, init, prev, options) {
// lazy load
MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentArray'));
// Skip casting if `value` is the same as the previous value, no need to cast. See gh-9266
if (value?.[arrayPathSymbol] != null && value === prev) {
return value;
}
let selected;
let subdoc;
options = options || {};
const path = options.path || this.path;
if (!Array.isArray(value)) {
if (!init && !SchemaDocumentArray.options.castNonArrays) {
throw new CastError('DocumentArray', value, this.path, null, this);
}
// gh-2442 mark whole array as modified if we're initializing a doc from
// the db and the path isn't an array in the document
if (!!doc && init) {
doc.markModified(path);
}
return this.cast([value], doc, init, prev, options);
}
// We need to create a new array, otherwise change tracking will
// update the old doc (gh-4449)
if (!options.skipDocumentArrayCast || utils.isMongooseDocumentArray(value)) {
value = new MongooseDocumentArray(value, path, doc, this);
}
if (prev != null) {
value[arrayAtomicsSymbol] = prev[arrayAtomicsSymbol] || {};
}View on GitHub (pinned to 49cdab0136)
Solutions
- Wrap the value before assignment: `doc.tags = [value]`
- If the object-vs-array ambiguity is expected in your API, re-enable lenient casting (remove `castNonArrays: false` or set it true)
- Validate `Array.isArray(payload.tags)` at the request boundary and return 400 with a clear message
- For bulk writes, map incoming objects to `[obj]` in a transform layer
Example fix
// before (schema has castNonArrays: false)
doc.tags = req.body.tags; // client sent { label: 'x' }
// after
const tags = Array.isArray(req.body.tags) ? req.body.tags : [req.body.tags];
doc.tags = tags; Defensive patterns
Strategy: validation
Validate before calling
function toArrayValue(v) {
return Array.isArray(v) ? v : [v];
}
doc.tags = toArrayValue(req.body.tags); // safe under castNonArrays: false Type guard
function isArrayOfPOJOs(v) {
return Array.isArray(v) && v.every(x => x && typeof x === 'object' && !Array.isArray(x));
} Try / catch
try { doc.tags = value; } catch (err) { if (err.name === 'CastError' && err.kind === 'DocumentArray') { return badRequest(`${err.path} must be an array`); } throw err; } Prevention
- Wrap single objects into arrays at the request boundary
- Decide castNonArrays policy once and document it per endpoint
- Validate array shape in your request-schema layer (zod/joi/ajv)
When it happens
Trigger: With strict casting enabled: `doc.tags = { label: 'x' }` or `doc.tags = 'x'` where the schema is `tags: { type: [tagSchema], castNonArrays: false }`; also `Model.updateOne({ tags: 'not-an-array' })` style casts. Enables via `mongoose.Schema.Types.DocumentArray.set('castNonArrays', false)`.
Common situations: API clients serializing a single-element list as a plain object instead of an array; turning off castNonArrays to harden an API and then discovering a client still sends objects; form submissions collapsing one-item arrays (classic HTML forms); strict mode adopted after a schema refactor from single subdoc to array.
Related errors
- Cast to embedded failed for value "${value}" (type ${valueTy
- Query filter must be an object, got an array ${util.inspect(
- Cast to Array failed for value "${value}" at path "${path}"
- Cast to Object failed for value "${value}" at path "${path}.
- Mongoose only supports BigInts between -9223372036854775808
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/fe48da9a5c571ef1.
Report an issue: GitHub.