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

  1. Wrap the value before assignment: `doc.tags = [value]`
  2. If the object-vs-array ambiguity is expected in your API, re-enable lenient casting (remove `castNonArrays: false` or set it true)
  3. Validate `Array.isArray(payload.tags)` at the request boundary and return 400 with a clear message
  4. 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

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


AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21). Data as JSON: /api/errors/fe48da9a5c571ef1. Report an issue: GitHub.