RocketChat/Rocket.Chat · error · 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 /api/v1/livechat/rooms when the optional `customFields` query parameter cannot be parsed as JSON, or parses to something that is not a plain object (an array, null, a string, a number). The endpoint runs JSON.parse(customFields) and also re-raises this same message when the parsed value fails the object shape check, so the error text always points at 'valid JSON' even when the real problem was that you sent an array or `null`.

Solutions

  1. Send the value as a URL-encoded flat JSON object of string keys to string values: customFields=%7B%22advisor%22%3A%22c82b87e0%22%7D
  2. Always build the query string with encodeURIComponent(JSON.stringify(filter)) in your client
  3. If you want all rooms with any custom fields, drop the parameter entirely instead of sending {} or null
  4. Verify with a quick client-side JSON.parse plus Object.keys check before firing the request

Example fix

// before
const url = `${server}/api/v1/livechat/rooms?customFields={"advisor":"c82b87e0"}`;
// after
const url = `${server}/api/v1/livechat/rooms?customFields=${encodeURIComponent(JSON.stringify({ advisor: 'c82b87e0' }))}`;
Defensive patterns

Strategy: validation

Validate before calling

const customFields = { advisor: 'c82b87e0' };
const encoded = encodeURIComponent(JSON.stringify(customFields));
// only values JSON.parse can read AND that are plain objects survive the server check
await fetch(`${server}/api/v1/livechat/rooms?customFields=${encoded}`, { headers });

Type guard

const isPlainStringMap = (v: unknown): v is Record<string, string> =>
  typeof v === 'object' && v !== null && !Array.isArray(v) && Object.values(v).every((x) => typeof x === 'string');

function assertCustomFieldsParam(raw: string): Record<string, string> {
  const parsed: unknown = JSON.parse(raw); // throws locally instead of a 400 remotely
  if (!isPlainStringMap(parsed)) throw new TypeError('customFields must be a JSON object of string -> string');
  return parsed;
}

Try / catch

try {
  const res = await fetch(url);
  const body = await res.json();
  if (!body.success && /must be a valid JSON/.test(body.error)) {
    // fix encoding of customFields and retry once
  }
} catch (e) { /* network errors only; param errors arrive as 400 bodies */ }

Prevention

When it happens

Trigger: Calling GET /api/v1/livechat/rooms?customFields=... with: unquoted keys ({advisor:c82b87e0}), a JSON array ([{"advisor":"x"}]), the literal string null, a truncated payload caused by not URL-encoding (the & or " characters cut the query string), or double-encoded JSON ({\"advisor\":\"x\"}).

Common situations: Building the URL by string concatenation without encodeURIComponent so {"a":"b"} is clipped at the first encoded character; copying a Mongo filter that wraps fields in an array; testing with curl and losing quotes to shell interpolation; values that contain & (e.g. session ids) splitting the query string.

Related errors


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

Appendix: 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 b2c16d5842)