{"record":{"id":"0db5590703d0c5ac","repo":"RocketChat/Rocket.Chat","slug":"error-duplicate-role-names-not-allowed","errorCode":"error-duplicate-role-names-not-allowed","errorMessage":"Role name already exists","messagePattern":"Role name already exists","errorType":"exception","errorClass":"Meteor.Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/ee/server/api/roles.ts","lineNumber":127,"sourceCode":"\t\t\t401: validateUnauthorizedErrorResponse,\n\t\t\t400: validateBadRequestErrorResponse,\n\t\t},\n\t},\n\tasync function action() {\n\t\tif (!License.hasModule('custom-roles')) {\n\t\t\tthrow new Meteor.Error('error-action-not-allowed', 'This is an enterprise feature');\n\t\t}\n\n\t\tconst { userId } = this;\n\n\t\tif (!userId || !(await hasPermissionAsync(userId, 'access-permissions'))) {\n\t\t\tthrow new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed');\n\t\t}\n\n\t\tconst { name, scope, description, mandatory2fa } = this.bodyParams;\n\n\t\tif (await Roles.findOneByIdOrName(name)) {\n\t\t\tthrow new Meteor.Error('error-duplicate-role-names-not-allowed', 'Role name already exists');\n\t\t}\n\n\t\tconst roleData = {\n\t\t\tdescription: description || '',\n\t\t\t...(mandatory2fa !== undefined && { mandatory2fa }),\n\t\t\tname,\n\t\t\tscope: scope || 'Users',\n\t\t\tprotected: false,\n\t\t};\n\n\t\tconst options = {\n\t\t\tbroadcastUpdate: settings.get<boolean>('UI_DisplayRoles'),\n\t\t};\n\n\t\tconst role = await insertRoleAsync(roleData, options);\n\n\t\treturn API.v1.success({ role });\n\t},","sourceCodeStart":109,"sourceCodeEnd":145,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/ee/server/api/roles.ts#L109-L145","documentation":"roles.create rejects names that collide with any existing role: Roles.findOneByIdOrName(name) matches by _id OR by name, so re-using 'admin', 'moderator', any other built-in name, or an already-created custom role name throws 'error-duplicate-role-names-not-allowed' ('Role name already exists').","triggerScenarios":"POST /v1/roles.create with a name equal to an existing role's name or _id, e.g. 'moderator' or the literal id string of another role.","commonSituations":"Re-running a non-idempotent provisioning script; picking obvious names that collide with built-in roles; name compared exactly (case-sensitive) so 'Moderator' vs 'moderator' confusion.","solutions":["Choose a unique name, e.g. prefixed with the integration ('zendesk-auditor')","Fetch GET /v1/roles.list and check the name does not exist before creating","If the role already exists and you want to change it, call POST /v1/roles.update instead"],"exampleFix":"// before\nawait POST('/api/v1/roles.create', { name: 'moderator' }); // built-in role → duplicate\n\n// after\nconst { roles } = await GET('/api/v1/roles.list');\nif (!roles.some((role) => role.name === 'moderator')) {\n  await POST('/api/v1/roles.create', { name: 'moderator' });\n}","handlingStrategy":"validation","validationCode":"import { Roles } from '@rocket.chat/models';\n\nif (await Roles.findOneByIdOrName(newRoleName)) {\n  throw new Error(`role name '${newRoleName}' already taken`);\n}\nawait POST('/api/v1/roles.create', { name: newRoleName });","typeGuard":null,"tryCatchPattern":"try {\n  await POST('/api/v1/roles.create', payload);\n} catch (error) {\n  if (error.error === 'error-duplicate-role-names-not-allowed') {\n    return GET('/api/v1/roles.list'); // idempotent path: reuse the existing role\n  }\n  throw error;\n}","preventionTips":["Namespace custom role names (e.g. 'billing-viewer') to avoid built-in collisions","Make provisioning scripts idempotent: check-then-create instead of blind create","Remember the check matches _id AND name"],"tags":["roles","duplicate","validation","rbac"],"backgroundTag":"duplicate-resource","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"}