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

  1. 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.
  2. 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

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


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