{"record":{"id":"97ee509ca9c8cbd8","repo":"Automattic/mongoose","slug":"query-filter-must-be-an-object-got-an-array-uti","errorCode":null,"errorMessage":"Query filter must be an object, got an array ${util.inspect(obj)}","messagePattern":"Query filter must be an object, got an array (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"lib/cast.js","lineNumber":38,"sourceCode":"\nconst ALLOWED_GEOWITHIN_GEOJSON_TYPES = ['Polygon', 'MultiPolygon'];\n\n/**\n * Handles internal casting for query filters.\n *\n * @param {Schema} schema\n * @param {object} obj Object to cast\n * @param {object} [options] the query options\n * @param {boolean|\"throw\"} [options.strict] Whether to enable all strict options\n * @param {boolean|\"throw\"} [options.strictQuery] Enable strict Queries\n * @param {boolean} [options.sanitizeFilter] avoid adding implicit query selectors ($in)\n * @param {boolean} [options.upsert]\n * @param {Query} [context] passed to setters\n * @api private\n */\nmodule.exports = function cast(schema, obj, options, context) {\n  if (Array.isArray(obj)) {\n    throw new Error('Query filter must be an object, got an array ' + util.inspect(obj));\n  }\n\n  if (obj == null) {\n    return obj;\n  }\n\n  if (schema?.discriminators != null && obj[schema.options.discriminatorKey] != null) {\n    schema = getSchemaDiscriminatorByValue(schema, obj[schema.options.discriminatorKey]) || schema;\n  }\n\n  const paths = Object.keys(obj);\n  let i = paths.length;\n  let _keys;\n  let any$conditionals;\n  let schematype;\n  let nested;\n  let path;\n  let type;","sourceCodeStart":20,"sourceCodeEnd":56,"githubUrl":"https://github.com/Automattic/mongoose/blob/49cdab01366679723b487ecb754b38570f783289/lib/cast.js#L20-L56","documentation":"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.","triggerScenarios":"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.","commonSituations":"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).","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"],"exampleFix":"// before\nconst filters = [{ name: 'x' }, { age: 1 }];\nawait Model.find(filters);\n\n// after\nawait Model.find({ $or: [{ name: 'x' }, { age: 1 }] });","handlingStrategy":"type-guard","validationCode":"function isPlainFilter(v) {\n  return v != null && typeof v === 'object' && !Array.isArray(v);\n}\nfunction normalizeFilter(v) {\n  if (!isPlainFilter(v)) {\n    throw new TypeError(`Query filter must be an object, got ${Array.isArray(v) ? 'array' : typeof v}`);\n  }\n  return v;\n}\nawait Model.find(normalizeFilter(req.body.filter));","typeGuard":"const isPlainFilter = (v) =>\n  v != null && typeof v === 'object' && !Array.isArray(v);","tryCatchPattern":"try {\n  await Model.find(filter);\n} catch (err) {\n  if (err.message.startsWith('Query filter must be an object')) {\n    return res.status(400).json({ error: 'filter must be a JSON object' });\n  }\n  throw err;\n}","preventionTips":["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[]"],"tags":["mongoose","query","cast","filter","validation"],"backgroundTag":"invalid-query-filter","analyzedSha":"49cdab01366679723b487ecb754b38570f783289","analyzedAt":"2026-08-21T22:54:00.882Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}