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

  1. Send customFields as a URL-encoded JSON object: customFields=%7B%22department%22%3A%22sales%22%7D.
  2. Build the object programmatically (JSON.stringify) rather than template-concatenating.
  3. Validate typeof parsed === 'object' && !Array.isArray && !== null on the client before sending.
  4. 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

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


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/903a954f04510b0a. Report an issue: GitHub.