Automattic/mongoose · error · MongooseError

Virtual path "${name}" conflicts with a real path in the sch

Error message

Virtual path "${name}" conflicts with a real path in the schema

What it means

Before registering a virtual, mongoose checks pathType(name); a result of 'real' means a stored path already occupies that exact name. A virtual cannot shadow a persisted field (its getters/setters would fight the real path), so the definition is refused.

Source

Thrown at lib/schema.js:2693

        this.paths[cur].schema.virtual(remnant, options);
        break;
      } else if (this.paths[cur].$isSchemaMap) {
        const remnant = parts.slice(i + 2).join('.');
        this.paths[cur].$__schemaType.schema.virtual(remnant, options);
        break;
      }

      cur += '.' + parts[i + 1];
    }

    return virtual;
  }

  const virtuals = this.virtuals;
  const parts = name.split('.');

  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`);
    }
  }

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Rename the virtual to a free name (e.g. 'displayName', 'nameVirtual').
  2. Remove or rename the stored field if the virtual replaces it.
  3. When names are dynamic, guard with `if (schema.pathType(name) !== 'real') schema.virtual(name)`.

Example fix

// before
const s = new Schema({ name: String });
s.virtual('name'); // conflicts with the stored path

// after
const s = new Schema({ name: String });
s.virtual('displayName').get(function() { return this.name?.toUpperCase(); });
Defensive patterns

Strategy: validation

Validate before calling

const safeVirtual = (schema, name, opts) => {
  if (schema.pathType(name) === 'real') {
    throw new Error(`path ${name} already exists as a real path; pick another virtual name`);
  }
  return schema.virtual(name, opts);
};

Prevention

When it happens

Trigger: `new Schema({ name: String })` followed by `schema.virtual('name')`; `schema.virtual('a.b')` when 'a.b' is a real nested path; plugins registering virtuals over existing fields on schemas they do not control.

Common situations: Adding a computed getter that collides with a stored field (e.g. a stored 'fullName' plus a computed one); plugin/schema composition across teams; renaming fields but keeping old virtuals under the old names.

Related errors


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