Automattic/mongoose · error · Error
Query filter must be an object, got an array ${util.inspect(
Error message
Query filter must be an object, got an array ${util.inspect(obj)} What it means
Thrown by Mongoose's internal cast() (lib/cast.js) when the object passed as a query filter is an array. Query filters must be plain documents; arrays are rejected at the very top of casting before schema path resolution. The error is a plain Error (not MongooseError) and includes util.inspect of the offending array.
Source
Thrown at lib/cast.js:38
const ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];
/**
* Handles internal casting for query filters.
*
* @param {Schema} schema
* @param {object} obj Object to cast
* @param {object} [options] the query options
* @param {boolean|"throw"} [options.strict] Whether to enable all strict options
* @param {boolean|"throw"} [options.strictQuery] Enable strict Queries
* @param {boolean} [options.sanitizeFilter] avoid adding implicit query selectors ($in)
* @param {boolean} [options.upsert]
* @param {Query} [context] passed to setters
* @api private
*/
module.exports = function cast(schema, obj, options, context) {
if (Array.isArray(obj)) {
throw new Error('Query filter must be an object, got an array ' + util.inspect(obj));
}
if (obj == null) {
return obj;
}
if (schema?.discriminators != null && obj[schema.options.discriminatorKey] != null) {
schema = getSchemaDiscriminatorByValue(schema, obj[schema.options.discriminatorKey]) || schema;
}
const paths = Object.keys(obj);
let i = paths.length;
let _keys;
let any$conditionals;
let schematype;
let nested;
let path;
let type;View on GitHub (pinned to 49cdab0136)
Solutions
- Pass a single object: Model.find({ name: 'x' })
- For matching any of several filters use $or: Model.find({ $or: [{ name: 'x' }, { age: 1 }] })
- Validate/filter request bodies before forwarding: reject or wrap non-object filters at the API boundary
Example fix
// before
const filters = [{ name: 'x' }, { age: 1 }];
await Model.find(filters);
// after
await Model.find({ $or: [{ name: 'x' }, { age: 1 }] }); Defensive patterns
Strategy: type-guard
Validate before calling
function isPlainFilter(v) {
return v != null && typeof v === 'object' && !Array.isArray(v);
}
function normalizeFilter(v) {
if (!isPlainFilter(v)) {
throw new TypeError(`Query filter must be an object, got ${Array.isArray(v) ? 'array' : typeof v}`);
}
return v;
}
await Model.find(normalizeFilter(req.body.filter)); Type guard
const isPlainFilter = (v) => v != null && typeof v === 'object' && !Array.isArray(v);
Try / catch
try {
await Model.find(filter);
} catch (err) {
if (err.message.startsWith('Query filter must be an object')) {
return res.status(400).json({ error: 'filter must be a JSON object' });
}
throw err;
} Prevention
- Validate request bodies at the API boundary: filters must be JSON objects
- Use { $or: [...] } for any-of filters — arrays are never valid filters
- In TypeScript type filters as Record<string, unknown>, not any[]
When it happens
Trigger: Model.find([{ name: 'x' }]) (array wrapping the filter); Model.find([{ name: 'x' }, { age: 1 }]) (intending multiple filters); Model.updateOne([{ a: 1 }], { $set: { b: 2 } }); findOne(someArray) where an API sent JSON array as the filter; spread of an array variable into the filter position.
Common situations: Frontends sending JSON arrays where the backend forwards req.body straight into find(); intending OR-semantics and assuming an array means $or; mixing up argument order (filter, update, options); copying driver examples that pass arrays to aggregate() (legal) into find() (illegal).
Related errors
- Cast to Array failed for value "${value}" at path "${path}"
- Cast to Object failed for value "${value}" at path "${path}.
- Must provide a filter object.
- Invalid atomic update value for ${op}. Expected an object, r
- Can't use ${$conditional} with Buffer.
AI-assisted analysis of Automattic/mongoose@49cdab0136 (2026-08-21).
Data as JSON: /api/errors/97ee509ca9c8cbd8.
Report an issue: GitHub.