RocketChat/Rocket.Chat · warning · Error
The "customFields" query parameter must be a valid JSON.
Error message
The "customFields" query parameter must be a valid JSON.
What it means
Thrown by GET livechat/rooms when the customFields query param either fails JSON.parse or parses to a non-object (array, primitive, null). The try/catch wraps both JSON.parse and the shape check, so any malformed JSON or wrong-typed value surfaces as this generic message ('Invalid custom fields' is caught and re-thrown as this).
Source
Thrown at apps/meteor/server/api/v1/omnichannel/rooms.ts:56
const hasAdminAccess = await hasPermissionAsync(this.user, 'view-livechat-rooms');
const hasAgentAccess = (await hasPermissionAsync(this.user, 'view-l-room')) && agents?.includes(this.userId) && agents?.length === 1;
if (!hasAdminAccess && !hasAgentAccess) {
return API.v1.forbidden();
}
let parsedCf: { [key: string]: string } | undefined = undefined;
if (customFields) {
try {
const parsedCustomFields = JSON.parse(customFields) as { [key: string]: string };
if (typeof parsedCustomFields !== 'object' || Array.isArray(parsedCustomFields) || parsedCustomFields === null) {
throw new Error('Invalid custom fields');
}
// Model's already checking for the keys, so we don't need to do it here.
parsedCf = parsedCustomFields;
} catch (e) {
throw new Error('The "customFields" query parameter must be a valid JSON.');
}
}
return API.v1.success(
await findRooms({
agents,
roomName,
departmentId,
...(isBoolean(open) && { open: open === true || open === 'true' }),
createdAt: createdAtParam,
closedAt: closedAtParam,
tags,
customFields: parsedCf,
onhold,
queued,
units,
query,
options: { offset, count, sort, fields },View on GitHub (pinned to f9d3ec372b)
Solutions
- Send customFields as a URL-encoded JSON object: customFields=%7B%22department%22%3A%22sales%22%7D.
- Build the object programmatically (JSON.stringify) rather than template-concatenating.
- Validate typeof parsed === 'object' && !Array.isArray && !== null on the client before sending.
- Omit the param when no custom-field filter is needed.
Example fix
// before
GET('/api/v1/livechat/rooms?customFields=' + rawInput);
// after
const cf = JSON.parse(rawInput);
if (typeof cf !== 'object' || Array.isArray(cf) || cf === null) throw new ClientError('bad cf');
GET('/api/v1/livechat/rooms?customFields=' + encodeURIComponent(JSON.stringify(cf))); Defensive patterns
Strategy: type-guard
Validate before calling
function buildCustomFieldsParam(input: unknown) {
if (input == null) return undefined;
if (typeof input !== 'object' || Array.isArray(input)) throw new ClientError('customFields must be object');
return encodeURIComponent(JSON.stringify(input));
} Type guard
const isCustomFieldsMap = (v: unknown): v is Record<string, string> => typeof v === 'object' && v !== null && !Array.isArray(v) && Object.values(v).every((x) => typeof x === 'string');
Try / catch
try {
await GET('/api/v1/livechat/rooms', { customFields: buildCustomFieldsParam(cf) });
} catch (e) {
if (/must be a valid JSON/i.test(e.message)) { showCfError(); return; }
throw e;
} Prevention
- Build the param via JSON.stringify of a validated object — never template-concatenate.
- URL-encode the serialized JSON.
- Omit the param when no custom-field filter is needed.
When it happens
Trigger: GET livechat/rooms?customFields=<not json> (e.g. 'foo', '{key:val}' unquoted, '[]' array, '"str"'). Any value that JSON.parse rejects OR that parses to a non-plain-object triggers it.
Common situations: Client concatenates key=value pairs instead of building a JSON object; user typed raw text; frontend sent an array of pairs instead of a map; missing URL encoding of braces/quotes breaks the parse.
Related errors
- The "${property}.start" query parameter must be a valid date
- The "${property}.end" query parameter must be a valid date.
- invalid-chart-name
- error-contact-not-found
- error-visitor-not-found
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/903a954f04510b0a.
Report an issue: GitHub.