{"record":{"id":"6015f41111d66c60","repo":"Automattic/mongoose","slug":"path-path-is-not-an-array","errorCode":null,"errorMessage":"Path \"${path}\" is not an array","messagePattern":"Path \"(.+?)\" is not an array","errorType":"exception","errorClass":"MongooseError","httpStatus":null,"severity":"error","filePath":"lib/schema.js","lineNumber":2709,"sourceCode":"\n  if (this.pathType(name) === 'real') {\n    throw new MongooseError('Virtual path \"' + name + '\"' +\n      ' conflicts with a real path in the schema');\n  }\n\n  virtuals[name] = parts.reduce(function(mem, part, i) {\n    mem[part] || (mem[part] = (i === parts.length - 1)\n      ? new VirtualType(options, name)\n      : {});\n    return mem[part];\n  }, this.tree);\n\n  if (options?.applyToArray && parts.length > 1) {\n    const path = this.path(parts.slice(0, -1).join('.'));\n    if (path?.$isMongooseArray) {\n      return path.virtual(parts[parts.length - 1], options);\n    } else {\n      throw new MongooseError(`Path \"${path}\" is not an array`);\n    }\n  }\n\n  return virtuals[name];\n};\n\n/**\n * Returns the virtual type with the given `name`.\n *\n * @param {string} name The name of the Virtual to get\n * @return {VirtualType|null}\n */\n\nSchema.prototype.virtualpath = function(name) {\n  return Object.hasOwn(this.virtuals, name) ? this.virtuals[name] : null;\n};\n\n/**","sourceCodeStart":2691,"sourceCodeEnd":2727,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/schema.js#L2691-L2727","documentation":"With `applyToArray: true`, a dotted virtual 'a.b' is attached to the array path 'a' itself (callable on the MongooseArray) instead of to each element. That only works when the parent segment resolves to a Mongoose array schematype; otherwise mongoose throws, echoing the resolved parent (which prints 'null' when the parent path does not exist at all).","triggerScenarios":"`schema.virtual('items.first', { applyToArray: true })` where 'items' is a nested object or single subdocument rather than an array; a parent-segment typo ('item.upper' when the path is 'items') so this.path() returns null.","commonSituations":"A schema that originally had arrays later changed to a single embedded doc while the virtual kept applyToArray; copy-paste of the array-virtual pattern onto non-array paths.","solutions":["Declare the parent as an array of subdocuments: `items: [new Schema({ ... })]`.","Drop applyToArray when you want the virtual applied to each element instead of the array itself.","Check first: `const parent = schema.path(name.split('.').slice(0, -1).join('.'))` and require parent?.$isMongooseArray."],"exampleFix":"// before\nconst s = new Schema({ item: { first: String } }); // not an array\ns.virtual('item.upper', { applyToArray: true });\n\n// after\nconst s = new Schema({ items: [{ first: String }] });\ns.virtual('items.upper', { applyToArray: true });","handlingStrategy":"validation","validationCode":"const canApplyToArray = (schema, dottedName) => {\n  const parent = dottedName.split('.').slice(0, -1).join('.');\n  return schema.path(parent)?.$isMongooseArray === true;\n};\nif (!canApplyToArray(schema, 'items.upper')) throw new Error('parent path is not an array');\nschema.virtual('items.upper', { applyToArray: true });","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep applyToArray virtuals in the same module as the array declaration so schema edits stay in sync.","Write a schema-shape unit test asserting the parents of applyToArray virtuals are arrays.","Remember a missing parent prints as null in the message - treat that as a name typo."],"tags":["virtuals","apply-to-array","arrays","schema-definition"],"backgroundTag":"virtual-on-non-array","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}