{"record":{"id":"fe48da9a5c571ef1","repo":"Automattic/mongoose","slug":"cast-to-documentarray-failed-for-value-value","errorCode":null,"errorMessage":"Cast to DocumentArray failed for value \"${value}\" (type ${valueType}) at path \"${path}\"","messagePattern":"Cast to DocumentArray failed for value \"(.+?)\" \\(type (.+?)\\) at path \"(.+?)\"","errorType":"exception","errorClass":"CastError","httpStatus":null,"severity":"error","filePath":"lib/schema/documentArray.js","lineNumber":400,"sourceCode":"SchemaDocumentArray.prototype.cast = function(value, doc, init, prev, options) {\n  // lazy load\n  MongooseDocumentArray || (MongooseDocumentArray = require('../types/documentArray'));\n\n  // Skip casting if `value` is the same as the previous value, no need to cast. See gh-9266\n  if (value?.[arrayPathSymbol] != null && value === prev) {\n    return value;\n  }\n\n  let selected;\n  let subdoc;\n\n  options = options || {};\n\n  const path = options.path || this.path;\n\n  if (!Array.isArray(value)) {\n    if (!init && !SchemaDocumentArray.options.castNonArrays) {\n      throw new CastError('DocumentArray', value, this.path, null, this);\n    }\n    // gh-2442 mark whole array as modified if we're initializing a doc from\n    // the db and the path isn't an array in the document\n    if (!!doc && init) {\n      doc.markModified(path);\n    }\n    return this.cast([value], doc, init, prev, options);\n  }\n\n  // We need to create a new array, otherwise change tracking will\n  // update the old doc (gh-4449)\n  if (!options.skipDocumentArrayCast || utils.isMongooseDocumentArray(value)) {\n    value = new MongooseDocumentArray(value, path, doc, this);\n  }\n\n  if (prev != null) {\n    value[arrayAtomicsSymbol] = prev[arrayAtomicsSymbol] || {};\n  }","sourceCodeStart":382,"sourceCodeEnd":418,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/schema/documentArray.js#L382-L418","documentation":"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.","triggerScenarios":"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)`.","commonSituations":"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.","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"],"exampleFix":"// before (schema has castNonArrays: false)\ndoc.tags = req.body.tags; // client sent { label: 'x' }\n\n// after\nconst tags = Array.isArray(req.body.tags) ? req.body.tags : [req.body.tags];\ndoc.tags = tags;","handlingStrategy":"validation","validationCode":"function toArrayValue(v) {\n  return Array.isArray(v) ? v : [v];\n}\ndoc.tags = toArrayValue(req.body.tags); // safe under castNonArrays: false","typeGuard":"function isArrayOfPOJOs(v) {\n  return Array.isArray(v) && v.every(x => x && typeof x === 'object' && !Array.isArray(x));\n}","tryCatchPattern":"try { doc.tags = value; } catch (err) { if (err.name === 'CastError' && err.kind === 'DocumentArray') { return badRequest(`${err.path} must be an array`); } throw err; }","preventionTips":["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)"],"tags":["mongoose","document-array","cast","castnonarrays","schema-option"],"backgroundTag":"mongoose-cast-error","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}