RocketChat/Rocket.Chat · error · Error
query must be an object
Error message
query must be an object
What it means
isValidQuery (apps/meteor/server/api/lib/isValidQuery.ts) validates query/selector objects against an allowlist of attributes and operators. Before validating anything it asserts the input is a record: isRecord(query). If the value is an array, string, number, boolean or null it throws this plain Error. The isRecord check (rather than instanceof Object) exists because EJSON-parsed and null-prototype objects fail instanceof; conversely, arrays are deliberately rejected.
Source
Thrown at apps/meteor/server/api/lib/isValidQuery.ts:15
import { isRecord } from '@rocket.chat/tools';
import { removeDangerousProps } from './cleanQuery';
type Query = { [k: string]: any };
export const isValidQuery: {
(query: Query, allowedAttributes: string[], allowedOperations: string[]): boolean;
errors: string[];
} = Object.assign(
(query: Query, allowedAttributes: string[], allowedOperations: string[]): boolean => {
isValidQuery.errors = [];
// query is an object with null prototype, so it wont be instance of Object
if (!isRecord(query)) {
throw new Error('query must be an object');
}
return verifyQuery(query, allowedAttributes, allowedOperations);
},
{
errors: [],
},
);
const verifyQuery = (query: Query, allowedAttributes: string[], allowedOperations: string[], parent = ''): boolean => {
return Object.entries(removeDangerousProps({ ...query })).every(([key, value]) => {
const path = parent ? `${parent}.${key}` : key;
if (key.startsWith('$')) {
if (!allowedOperations.includes(key)) {
isValidQuery.errors.push(`Invalid operation: ${key}`);
return false;
}
View on GitHub (pinned to b2c16d5842)
Solutions
- Make the validated value a JSON object — wrap arrays under an operator key: {"conditions": {"$and": [...]}}
- Default to {} (empty object) instead of [] or null when there are no filters
- JSON.stringify the whole selector once, not a string inside a string, before sending the query param
Example fix
// before
GET /api/v1/users.selector?selector={"term":"a","conditions":[{"name":"x"}]}
// after
GET /api/v1/users.selector?selector={"term":"a","conditions":{"name":"x"}}
// array filters go under an operator: {"conditions":{"$and":[{"name":"x"}]}} Defensive patterns
Strategy: type-guard
Validate before calling
const toConditions = (v: unknown): Record<string, unknown> =>
(typeof v === 'object' && v !== null && !Array.isArray(v)) ? v as Record<string, unknown> : {}; // never send [] or strings Type guard
const isRecord = (v: unknown): v is Record<string, unknown> =>
typeof v === 'object' && v !== null && !Array.isArray(v);
// before calling users.selector or building a query param:
if (!isRecord(selector.conditions)) selector.conditions = {}; Try / catch
try {
await client.get('/api/v1/users.selector', { params: { selector: JSON.stringify(selector) } });
} catch (e: any) {
if ((e?.response?.data?.error ?? '').includes('query must be an object')) {
throw new ValidationError('conditions must be a JSON object, not an array/string');
}
throw e;
} Prevention
- Always wrap array filters under an operator key ({"$and": [...]}) instead of passing the bare array
- Default filters to {} not [] or null
- Stringify the selector exactly once; double-stringified payloads parse back into strings
When it happens
Trigger: users.selector endpoint (apps/meteor/server/api/v1/users.ts:1702) with a selector whose conditions field is a JSON array or string (e.g. selector={"term":"a","conditions":[...]}); or any parseJsonQuery-backed endpoint with ALLOW_UNSAFE_QUERY_AND_FIELDS_API_PARAMS=TRUE where the query param parses to a JSON array or scalar (query=%5B1,2%5D, query=%22foo%22).
Common situations: Building a $and/$or filter as an array and assigning it directly to conditions instead of wrapping it ({"$and": [...]}) ; passing a pre-stringified selector inside another JSON string so it double-parses into a string; client code that conditionally sends [] instead of {} for empty filters.
Related errors
- error-invalid-user
- The integration does not exists.
- error-invalid-param
- Message not found
- message-not-found
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/302ec48339ace937.
Report an issue: GitHub.