{"record":{"id":"08500d6c95417632","repo":"RocketChat/Rocket.Chat","slug":"param-params-customfields-key-must-be-an-obje","errorCode":null,"errorMessage":"Param \"${params.customFields.key}\" must be an object if provided","messagePattern":"Param \"(.+?)\" must be an object if provided","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"apps/meteor/server/api/v1/channels.ts","lineNumber":1026,"sourceCode":"\n\tconst team = teamId && (await Team.getInfoById(teamId));\n\tif (\n\t\t(!teamId && !(await hasPermissionAsync(params.user.value, 'create-c'))) ||\n\t\t(teamId && team && !(await hasPermissionAsync(params.user.value, 'create-team-channel', team.roomId)))\n\t) {\n\t\tthrow new Error('unauthorized');\n\t}\n\n\tif (!params.name?.value) {\n\t\tthrow new Error(`Param \"${params.name?.key}\" is required`);\n\t}\n\n\tif (params.members?.value && !Array.isArray(params.members.value)) {\n\t\tthrow new Error(`Param \"${params.members.key}\" must be an array if provided`);\n\t}\n\n\tif (params.customFields?.value && !(typeof params.customFields.value === 'object')) {\n\t\tthrow new Error(`Param \"${params.customFields.key}\" must be an object if provided`);\n\t}\n\n\tif (params.teams?.value && !Array.isArray(params.teams.value)) {\n\t\tthrow new Error(`Param ${params.teams.key} must be an array`);\n\t}\n}\n\nasync function createChannel(\n\tuserId: string,\n\tparams: {\n\t\tname?: string;\n\t\tmembers?: string[];\n\t\tcustomFields?: Record<string, any>;\n\t\textraData?: Record<string, any>;\n\t\treadOnly?: boolean;\n\t\texcludeSelf?: boolean;\n\t},\n): Promise<{ channel: IRoom }> {","sourceCodeStart":1008,"sourceCodeEnd":1044,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/f9d3ec372bb580fa8d036f94cf03925a478ef768/apps/meteor/server/api/v1/channels.ts#L1008-L1044","documentation":"Thrown during channel creation when params.customFields.value is present but not an object. The guard uses typeof === 'object' WITHOUT an Array.isArray exclusion, so an array will pass this check (arrays are objects) - but a string, number, or boolean will fail. The param key name is interpolated.","triggerScenarios":"Submitting customFields as a JSON string (e.g., customFields: '{\"k\":\"v\"}') instead of an object, or as a primitive value.","commonSituations":"Double-serialized JSON (client JSON.stringify-ing customFields then the transport stringifying again); passing customFields: null trips typeof null === 'object' so it passes (latent bug); form libraries that coerce objects to strings.","solutions":["Send customFields as a JSON object: customFields: { department: 'eng' }.","If the client must send a string, parse it server-side before this check.","Audit for null: this guard lets null through - consider an explicit null exclusion upstream."],"exampleFix":"// before - request body (double-serialized)\n{ \"name\": \"room\", \"customFields\": \"{\\\"dept\\\":\\\"eng\\\"}\" }\n\n// after\n{ \"name\": \"room\", \"customFields\": { \"dept\": \"eng\" } }","handlingStrategy":"validation","validationCode":"function normalizeCustomFields(customFields) {\n  if (customFields == null) return undefined;\n  if (typeof customFields === 'string') {\n    // caller double-serialized - parse once\n    return JSON.parse(customFields);\n  }\n  if (typeof customFields === 'object' && !Array.isArray(customFields)) return customFields;\n  throw new Error('customFields must be a plain object');\n}","typeGuard":"function isPlainObject(value) {\n  return typeof value === 'object' && value !== null && !Array.isArray(value);\n}","tryCatchPattern":"try {\n  await api.createChannel({ name, customFields });\n} catch (e) {\n  if (/must be an object/.test(e.message)) {\n    customFields = typeof customFields === 'string' ? JSON.parse(customFields) : {};\n    return api.createChannel({ name, customFields });\n  }\n  throw e;\n}","preventionTips":["Send customFields as a JSON object, not a stringified one.","Exclude arrays and null explicitly - the server's typeof check lets both through.","Validate the shape client-side with a schema."],"tags":["channels","validation","param-type","custom-fields","create"],"backgroundTag":null,"analyzedSha":"f9d3ec372bb580fa8d036f94cf03925a478ef768","analyzedAt":"2026-08-12T19:07:17.372Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}