{"record":{"id":"22cd1a8cee5d16da","repo":"Automattic/mongoose","slug":"cannot-set-populate-virtual-as-a-property-of-an-ar","errorCode":null,"errorMessage":"Cannot set populate virtual as a property of an array","messagePattern":"Cannot set populate virtual as a property of an array","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/schema/array.js","lineNumber":568,"sourceCode":"  }\n};\n\n/**\n * Add a virtual to this array. Specifically to this array, not the individual elements.\n *\n * @param {string} name\n * @param {object} [options]\n * @api private\n */\n\nSchemaArray.prototype.virtual = function virtual(name, options) {\n  if (name instanceof VirtualType || getConstructorName(name) === 'VirtualType') {\n    return this.virtual(name.path, name.options);\n  }\n  options = new VirtualOptions(options);\n\n  if (utils.hasUserDefinedProperty(options, ['ref', 'refPath'])) {\n    throw new MongooseError('Cannot set populate virtual as a property of an array');\n  }\n\n  const virtual = new VirtualType(options, name);\n  if (this.virtuals === null) {\n    this.virtuals = {};\n  }\n  this.virtuals[name] = virtual;\n  return virtual;\n};\n\nfunction cast$all(val, context) {\n  if (!Array.isArray(val)) {\n    val = [val];\n  }\n\n  val = val.map((v) => {\n    if (!utils.isObject(v)) {\n      return v;","sourceCodeStart":550,"sourceCodeEnd":586,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/schema/array.js#L550-L586","documentation":"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.","triggerScenarios":"`schema.path('items').virtual('owner', { ref: 'User' })`; a schema.virtual call with applyToArray whose options include ref/refPath, landing on the array type.","commonSituations":"Trying to populate 'the owner of every element' from the parent array; migrating a document-level populate virtual onto an array during schema reshaping.","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."],"exampleFix":"// before\nconst itemSchema = new Schema({ ownerId: ObjectId });\nconst s = new Schema({ items: [itemSchema] });\ns.path('items').virtual('owner', { ref: 'User' }); // throws\n\n// after\nitemSchema.virtual('owner', { ref: 'User', localField: 'ownerId', foreignField: '_id', justOne: true });\n// then: await doc.populate('items.owner');","handlingStrategy":"validation","validationCode":"const assertNoPopulateOnArrayVirtual = (opts) => {\n  if (opts != null && ('ref' in opts || 'refPath' in opts)) {\n    throw new Error('populate virtuals must live on a document schema, not on an array');\n  }\n};\nschema.path('items').virtual('firstUpper');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["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."],"tags":["virtuals","populate","arrays","schema-definition"],"backgroundTag":"virtual-populate-misuse","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}