Automattic/mongoose · error · CastError

Cast to embedded failed for value "${value}" (type ${valueTy

Error message

Cast to embedded failed for value "${value}" (type ${valueType}) at path "${path}"

What it means

While casting each element of a document array, Mongoose constructs a subdocument (`new Constructor(element, ...)`) and wraps any construction failure as `CastError: embedded` pointing at the array path. So one element of your subdocument array could not be turned into a subdocument: typically a primitive element (string/number/array) where a plain object is required, or an element whose own nested casting threw during subdocument creation.

Source

Thrown at lib/schema/documentArray.js:492

        if (typeof prev?.id === 'function') {
          subdoc = prev.id(rawArray[i]._id);
        }

        if (prev && subdoc && utils.deepEqual(subdoc.toObject(_toObjectOptions), rawArray[i])) {
          // handle resetting doc with existing id and same data
          subdoc.set(rawArray[i]);
          // if set() is hooked it will have no return value
          // see gh-746
          rawArray[i] = subdoc;
        } else {
          try {
            subdoc = new Constructor(rawArray[i], value, undefined,
              undefined, i);
            // if set() is hooked it will have no return value
            // see gh-746
            rawArray[i] = subdoc;
          } catch (error) {
            throw new CastError('embedded', rawArray[i],
              value[arrayPathSymbol], error, this);
          }
        }
      }
    }
  }

  return value;
};

/*!
 * ignore
 */

SchemaDocumentArray.prototype.clone = function() {
  const options = Object.assign({}, this.options);
  const schematype = new this.constructor(
    this.path,

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Inspect `err.reason` on the CastError — it carries the underlying error and tells you which element shape failed
  2. Filter/normalize to plain objects before assignment: `arr.filter(v => utils.isPOJO(v))` or map primitives into `{ value: v }`
  3. Validate each row against the subschema (validateSync on a scratch document, or ajv) before bulk-inserting
  4. Add per-element schema validation (e.g. `validate: { validator: v => v != null && typeof v === 'object' }`) to fail with a clearer message

Example fix

// before
doc.items = importedRows; // some rows are plain strings

// after
doc.items = importedRows
  .filter(r => r && typeof r === 'object' && !Array.isArray(r))
  .map(r => ({ name: String(r.name ?? '') }));
Defensive patterns

Strategy: validation

Validate before calling

function sanitizeDocArray(rows) {
  return (Array.isArray(rows) ? rows : []).filter(
    r => r != null && typeof r === 'object' && !Array.isArray(r)
  );
}
doc.items = sanitizeDocArray(importedRows);

Type guard

function isPlainObjectRow(v) {
  return v != null && typeof v === 'object' && !Array.isArray(v) && !(v instanceof Date) && !Buffer.isBuffer(v);
}

Try / catch

try { doc.items = rows; } catch (err) { if (err.name === 'CastError' && err.kind === 'embedded') { console.error('element failed:', err.reason?.message); return badRequest(`invalid element in ${err.path}`); } throw err; }

Prevention

When it happens

Trigger: `doc.items = [{ name: 'ok' }, 'oops']` — array elements that are strings, numbers, or nested arrays instead of plain objects; update operations pushing malformed values (`$push: { items: 'oops' }`); import pipelines feeding mixed rows. The original failure is preserved on the error's `reason` property (and `originalError` fields), which names the real cause.

Common situations: CSV/JSON imports where some rows are scalars or null-ish sentinels; APIs returning heterogeneous arrays; mapping over unvalidated external data into embedded arrays; spread operator bugs that flatten objects into unexpected shapes.

Related errors


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