Automattic/mongoose · error · Error
Path '${path}' contains the same array filter multiple times
Error message
Path '${path}' contains the same array filter multiple times What it means
MongoDB requires each array filter identifier to appear at most once per dotted update path: 'a.$[x].b.$[x]' is illegal because one identifier cannot carry two different filters within the same path. Mongoose computes the base path each identifier maps to (for versioning and discriminator handling in updatedPathsByArrayFilter) and throws this Error when the same identifier matches multiple times in one path.
Source
Thrown at lib/helpers/update/updatedPathsByArrayFilter.js:19
'use strict';
const modifiedPaths = require('./modifiedPaths');
module.exports = function updatedPathsByArrayFilter(update) {
if (update == null) {
return {};
}
const updatedPaths = modifiedPaths(update);
return Object.keys(updatedPaths).reduce((cur, path) => {
const matches = path.match(/\$\[[^\]]+\]/g);
if (matches == null) {
return cur;
}
for (const match of matches) {
const firstMatch = path.indexOf(match);
if (firstMatch !== path.lastIndexOf(match)) {
throw new Error(`Path '${path}' contains the same array filter multiple times`);
}
cur[match.substring(2, match.length - 1)] = path.
substring(0, firstMatch - 1).
replace(/\$\[[^\]]+\]/g, '0');
}
return cur;
}, {});
};
View on GitHub (pinned to 49cdab0136)
Solutions
- Use a distinct identifier per array level: 'comments.$[c].replies.$[r].text' with arrayFilters: [{ 'c.approved': true }, { 'r.visible': true }]
- If both levels need the same condition, still use two identifiers and repeat the condition for each
- Split the operation into separate updateOne calls, one per array level, if a single path is unwieldy
Example fix
// before
await Model.updateOne({},
{ $set: { 'comments.$[c].replies.$[c].text': 'hi' } },
{ arrayFilters: [{ 'c.approved': true }] });
// after
await Model.updateOne({},
{ $set: { 'comments.$[c].replies.$[r].text': 'hi' } },
{ arrayFilters: [{ 'c.approved': true }, { 'r.visible': true }] }); Defensive patterns
Strategy: validation
Validate before calling
// Reject update paths that reuse an array filter identifier
function assertDistinctIdentifiers(update) {
for (const op of Object.keys(update)) {
for (const path of Object.keys(update[op] ?? {})) {
const ids = path.match(/\$\[([^\]]+)\]/g) ?? [];
const seen = new Set();
for (const id of ids) {
if (seen.has(id)) throw new Error(`Path '${path}' reuses identifier ${id}`);
seen.add(id);
}
}
}
} Type guard
const hasUniqueFilterIds = (path) => { const ids = path.match(/\$\[([^\]]+)\]/g) ?? []; return new Set(ids).size === ids.length; }; Try / catch
try {
await Model.updateOne(f, u, { arrayFilters });
} catch (err) {
if (/same array filter multiple times/.test(err.message)) {
// rename the inner identifier (e.g. $[c].$[c] -> $[c].$[r]) and add its filter, then retry
} else throw err;
} Prevention
- Adopt a convention of one identifier per nesting level (elem, sub, ...) for array updates
- Never build update paths by string concatenation of '.$[x]' segments
- Review nested-array updates against the MongoDB arrayFilters docs before shipping
When it happens
Trigger: Model.updateOne({}, { $set: { 'comments.$[c].replies.$[c].text': 'hi' } }, { arrayFilters: [{ 'c.approved': true }] }) — identifier 'c' reused for both the comments and the replies level.
Common situations: Nested array updates where the first identifier is reused for the inner array level; template/string-built update paths that concatenate '.$[x]' repeatedly; porting raw shell queries without adding a second identifier.
Related errors
- Got null array filter in ${arrayFilters}
- Could not find path "${filterPath}" in schema
- a circular reference in the update value, updateValue: ${uti
- Invalid atomic update value for ${op}. Expected an object, r
- Invalid update pipeline operator: "${op}"
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/7b8ecb93bf3974ae.
Report an issue: GitHub.