Automattic/mongoose · error · MongooseError
Cannot set populate virtual as a property of an array
Error message
Cannot set populate virtual as a property of an array
What it means
Array-level virtuals (attached to the MongooseArray itself via SchemaArray#virtual) support plain computed getters/setters only. ref/refPath populate virtuals need a document field to join from, which an array cannot provide, so defining one on an array is rejected.
Source
Thrown at lib/schema/array.js:568
}
};
/**
* Add a virtual to this array. Specifically to this array, not the individual elements.
*
* @param {string} name
* @param {object} [options]
* @api private
*/
SchemaArray.prototype.virtual = function virtual(name, options) {
if (name instanceof VirtualType || getConstructorName(name) === 'VirtualType') {
return this.virtual(name.path, name.options);
}
options = new VirtualOptions(options);
if (utils.hasUserDefinedProperty(options, ['ref', 'refPath'])) {
throw new MongooseError('Cannot set populate virtual as a property of an array');
}
const virtual = new VirtualType(options, name);
if (this.virtuals === null) {
this.virtuals = {};
}
this.virtuals[name] = virtual;
return virtual;
};
function cast$all(val, context) {
if (!Array.isArray(val)) {
val = [val];
}
val = val.map((v) => {
if (!utils.isObject(v)) {
return v;View on GitHub (pinned to 49cdab0136)
Solutions
- Define the populate virtual on the embedded subdocument's schema (each element populates its own owner), or on the parent document with localField/foreignField that fan out correctly.
- Keep ref/refPath out of array-level virtuals - use those for computed values only.
Example fix
// before
const itemSchema = new Schema({ ownerId: ObjectId });
const s = new Schema({ items: [itemSchema] });
s.path('items').virtual('owner', { ref: 'User' }); // throws
// after
itemSchema.virtual('owner', { ref: 'User', localField: 'ownerId', foreignField: '_id', justOne: true });
// then: await doc.populate('items.owner'); Defensive patterns
Strategy: validation
Validate before calling
const assertNoPopulateOnArrayVirtual = (opts) => {
if (opts != null && ('ref' in opts || 'refPath' in opts)) {
throw new Error('populate virtuals must live on a document schema, not on an array');
}
};
schema.path('items').virtual('firstUpper'); Prevention
- Put populate virtuals next to the field they join from (the subdocument schema), not on containers.
- Reject any review that mixes applyToArray or array-level virtuals with ref/refPath - it always errors.
- For whole-array joins, model the join from the parent document instead.
When it happens
Trigger: `schema.path('items').virtual('owner', { ref: 'User' })`; a schema.virtual call with applyToArray whose options include ref/refPath, landing on the array type.
Common situations: Trying to populate 'the owner of every element' from the parent array; migrating a document-level populate virtual onto an array during schema reshaping.
Related errors
- Reference virtuals require `localField` option
- Reference virtuals require `foreignField` option
- Path "${path}" is not an array
- Virtual path "${name}" conflicts with a real path in the sch
- `enum` can only be set on an array of strings or numbers , n
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/22cd1a8cee5d16da.
Report an issue: GitHub.