Automattic/mongoose · error · MongooseError

Cannot use schema-level projections (`select: true` or `sele

Error message

Cannot use schema-level projections (`select: true` or `select: false`) within maps at path "${path}.${subpath}"

What it means

When interpreting a Map of subdocuments, Mongoose walks every path of the value schema and throws if any path declares an explicit `select: true` or `select: false`. Projection options on subpaths inside a Map are unsupported (projections are resolved per top-level path and Map subpaths do not map cleanly onto projection documents), so Mongoose refuses the schema at definition time rather than silently ignoring the option.

Source

Thrown at lib/schema/map.js:182

  const mapPath = path + '.$*';
  let _mapType = { type: {} };
  if (utils.hasUserDefinedProperty(obj, 'of')) {
    const isInlineSchema = utils.isPOJO(obj.of) &&
      utils.hasOwnKeys(obj.of) &&
      !utils.hasUserDefinedProperty(obj.of, schema.options.typeKey);
    if (isInlineSchema) {
      _mapType = { [schema.options.typeKey]: new Schema(obj.of) };
    } else if (utils.isPOJO(obj.of)) {
      _mapType = Object.assign({}, obj.of);
    } else {
      _mapType = { [schema.options.typeKey]: obj.of };
    }

    if (_mapType[schema.options.typeKey] && _mapType[schema.options.typeKey].instanceOfSchema) {
      const subdocumentSchema = _mapType[schema.options.typeKey];
      subdocumentSchema.eachPath((subpath, type) => {
        if (type.options.select === true || type.options.select === false) {
          throw new MongooseError('Cannot use schema-level projections (`select: true` or `select: false`) within maps at path "' + path + '.' + subpath + '"');
        }
      });
    }

    if (utils.hasUserDefinedProperty(obj, 'ref')) {
      _mapType.ref = obj.ref;
    }
  }
  this.$__schemaType = schema.interpretAsType(mapPath, _mapType, options);
};

module.exports = SchemaMap;

View on GitHub (pinned to 49cdab0136)

Solutions

  1. Remove `select` from the map's value schema paths; hide those fields at query time with an explicit projection on the parent (`.select('-data')` or field filtering in the serializer layer)
  2. Clone the value schema and strip select options before passing it as `of`: `const s = base.clone(); s.eachPath((p, t) => delete t.options.select)`
  3. Restructure to a plain subdocument array path (`items: [base]`) where select is honored, or keep secrets in a separate model
  4. Enforce a lint/review rule: no select options inside Map-of-subdocument definitions

Example fix

// before
const secretSchema = new Schema({ token: { type: String, select: false } });
new Schema({ apiKeys: { type: Map, of: secretSchema } });

// after
const secretSchema = new Schema({ token: String });
new Schema({ apiKeys: { type: Map, of: secretSchema } });
// and filter token out in the response layer (toJSON transform or serializer)
Defensive patterns

Strategy: validation

Validate before calling

function stripSelectOptions(subSchema) {
  subSchema.eachPath((p, type) => { delete type.options.select; });
  return subSchema;
}
new Schema({ data: { type: Map, of: stripSelectOptions(baseSchema.clone()) } });

Try / catch

try { new Schema({ data: { type: Map, of: valueSchema } }); } catch (err) { if (/schema-level projections/.test(err.message)) { stripSelectOptions(valueSchema); } else throw err; }

Prevention

When it happens

Trigger: `new Schema({ data: { type: Map, of: new Schema({ secret: { type: String, select: false } }) } })` — any Map whose `of` resolves to a schema containing a path with select true/false. Note the check runs on the *value* schema's paths via eachPath, so even nested subdocument paths with select trigger it.

Common situations: Reusing an existing subdocument schema (with hidden fields like password hashes or tokens) as the `of` type of a Map; adding `select: false` for security without noticing the schema is embedded in a Map; schema libraries sharing a base sub-schema across Map and non-Map parents.

Related errors


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