{"record":{"id":"91193bfbce2ef73a","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-scope","errorCode":"error-invalid-scope","errorMessage":"Invalid scope","messagePattern":"Invalid scope","errorType":"exception","errorClass":"MeteorError","httpStatus":null,"severity":"error","filePath":"apps/meteor/ee/server/lib/roles/insertRole.ts","lineNumber":20,"sourceCode":"import type { IRole } from '@rocket.chat/core-typings';\nimport { Roles } from '@rocket.chat/models';\n\nimport { isValidRoleScope } from '../../../../lib/roles/isValidRoleScope';\nimport { notifyOnRoleChanged } from '../../../../server/lib/notifyListener';\n\ntype InsertRoleOptions = {\n\tbroadcastUpdate?: boolean;\n};\n\nexport const insertRoleAsync = async (roleData: Omit<IRole, '_id' | '_updatedAt'>, options: InsertRoleOptions = {}): Promise<IRole> => {\n\tconst { name, scope, description, mandatory2fa } = roleData;\n\n\tif (await Roles.findOneByName(name)) {\n\t\tthrow new MeteorError('error-duplicate-role-names-not-allowed', 'Role name already exists');\n\t}\n\n\tif (!isValidRoleScope(scope)) {\n\t\tthrow new MeteorError('error-invalid-scope', 'Invalid scope');\n\t}\n\n\tconst role = await Roles.createWithRandomId(name, scope, description, false, mandatory2fa);\n\n\tvoid notifyOnRoleChanged(role);\n\n\tif (options.broadcastUpdate) {\n\t\tvoid api.broadcast('user.roleUpdate', {\n\t\t\ttype: 'changed',\n\t\t\t_id: role._id,\n\t\t});\n\t}\n\n\treturn role;\n};\n","sourceCodeStart":2,"sourceCodeEnd":36,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/ee/server/lib/roles/insertRole.ts#L2-L36","documentation":"insertRoleAsync validates the scope with isValidRoleScope (apps/meteor/lib/roles/isValidRoleScope.ts), which accepts exactly the strings 'Users' and 'Subscriptions'. Anything else - 'Global', 'global', 'users', undefined - throws MeteorError('error-invalid-scope', 'Invalid scope'). Scope determines whether the role is assigned to users or to room subscriptions, so only those two literals are meaningful.","triggerScenarios":"Calling insertRoleAsync with a scope outside ['Users', 'Subscriptions'] - case mismatches ('users'), legacy/free-text values ('Global', 'Room'), or the field omitted entirely when the caller assumed a default.","commonSituations":"Scripts ported from other systems that used different scope vocabulary; case-sensitive payloads built by hand; schemas that do not enforce the enum letting arbitrary strings through to the server.","solutions":["Set scope to exactly 'Users' or 'Subscriptions' depending on whether the role applies to users or subscriptions.","Enforce the enum at the API boundary (e.g. ajv enum schema) so invalid scopes fail as 400 input errors, not server errors.","When unsure, omit nothing: pick 'Users' for normal user roles - most custom roles use it."],"exampleFix":"// before\nawait insertRoleAsync({ name: 'auditor', scope: 'Global', description: 'x' }); // throws error-invalid-scope\n\n// after\nawait insertRoleAsync({ name: 'auditor', scope: 'Users', description: 'x' });","handlingStrategy":"validation","validationCode":"const isRoleScope = (scope: unknown): boolean => scope === 'Users' || scope === 'Subscriptions';\nif (!isRoleScope(scope)) {\n\t// reject the input before calling insertRoleAsync\n}","typeGuard":"type RoleScope = 'Users' | 'Subscriptions';\nconst isRoleScope = (scope: unknown): scope is RoleScope => scope === 'Users' || scope === 'Subscriptions';","tryCatchPattern":"try {\n\tawait insertRoleAsync(roleData);\n} catch (e: any) {\n\tif (e?.error === 'error-invalid-scope') throw new Meteor.Error(400, \"scope must be 'Users' or 'Subscriptions'\");\n\tthrow e;\n}","preventionTips":["Model scope as a closed union type ('Users' | 'Subscriptions') in payloads and forms.","Enforce the enum at the API boundary with a JSON-schema validator.","Watch for case and whitespace drift when scope values come from user input."],"tags":["roles","permissions","validation","enum","enterprise"],"backgroundTag":"invalid-enum-value","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}