Automattic/mongoose · error · MongooseError

Path "${path}" is not an array

Error message

Path "${path}" is not an array

What it means

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).

Source

Thrown at lib/schema.js:2709

  if (this.pathType(name) === 'real') {
    throw new MongooseError('Virtual path "' + name + '"' +
      ' conflicts with a real path in the schema');
  }

  virtuals[name] = parts.reduce(function(mem, part, i) {
    mem[part] || (mem[part] = (i === parts.length - 1)
      ? new VirtualType(options, name)
      : {});
    return mem[part];
  }, this.tree);

  if (options?.applyToArray && parts.length > 1) {
    const path = this.path(parts.slice(0, -1).join('.'));
    if (path?.$isMongooseArray) {
      return path.virtual(parts[parts.length - 1], options);
    } else {
      throw new MongooseError(`Path "${path}" is not an array`);
    }
  }

  return virtuals[name];
};

/**
 * Returns the virtual type with the given `name`.
 *
 * @param {string} name The name of the Virtual to get
 * @return {VirtualType|null}
 */

Schema.prototype.virtualpath = function(name) {
  return Object.hasOwn(this.virtuals, name) ? this.virtuals[name] : null;
};

/**

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Declare the parent as an array of subdocuments: `items: [new Schema({ ... })]`.
  2. Drop applyToArray when you want the virtual applied to each element instead of the array itself.
  3. Check first: `const parent = schema.path(name.split('.').slice(0, -1).join('.'))` and require parent?.$isMongooseArray.

Example fix

// before
const s = new Schema({ item: { first: String } }); // not an array
s.virtual('item.upper', { applyToArray: true });

// after
const s = new Schema({ items: [{ first: String }] });
s.virtual('items.upper', { applyToArray: true });
Defensive patterns

Strategy: validation

Validate before calling

const canApplyToArray = (schema, dottedName) => {
  const parent = dottedName.split('.').slice(0, -1).join('.');
  return schema.path(parent)?.$isMongooseArray === true;
};
if (!canApplyToArray(schema, 'items.upper')) throw new Error('parent path is not an array');
schema.virtual('items.upper', { applyToArray: true });

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


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