RocketChat/Rocket.Chat · error · Meteor.Error

error-duplicate-role-names-not-allowed

error-duplicate-role-names-not-allowed

Error message

Role name already exists

What it means

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').

Solutions

  1. Choose a unique name, e.g. prefixed with the integration ('zendesk-auditor')
  2. Fetch GET /v1/roles.list and check the name does not exist before creating
  3. If the role already exists and you want to change it, call POST /v1/roles.update instead

Example fix

// before
await POST('/api/v1/roles.create', { name: 'moderator' }); // built-in role → duplicate

// after
const { roles } = await GET('/api/v1/roles.list');
if (!roles.some((role) => role.name === 'moderator')) {
  await POST('/api/v1/roles.create', { name: 'moderator' });
}
Defensive patterns

Strategy: validation

Validate before calling

import { Roles } from '@rocket.chat/models';

if (await Roles.findOneByIdOrName(newRoleName)) {
  throw new Error(`role name '${newRoleName}' already taken`);
}
await POST('/api/v1/roles.create', { name: newRoleName });

Try / catch

try {
  await POST('/api/v1/roles.create', payload);
} catch (error) {
  if (error.error === 'error-duplicate-role-names-not-allowed') {
    return GET('/api/v1/roles.list'); // idempotent path: reuse the existing role
  }
  throw error;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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

Appendix: source

Thrown at apps/meteor/ee/server/api/roles.ts:127

			401: validateUnauthorizedErrorResponse,
			400: validateBadRequestErrorResponse,
		},
	},
	async function action() {
		if (!License.hasModule('custom-roles')) {
			throw new Meteor.Error('error-action-not-allowed', 'This is an enterprise feature');
		}

		const { userId } = this;

		if (!userId || !(await hasPermissionAsync(userId, 'access-permissions'))) {
			throw new Meteor.Error('error-action-not-allowed', 'Accessing permissions is not allowed');
		}

		const { name, scope, description, mandatory2fa } = this.bodyParams;

		if (await Roles.findOneByIdOrName(name)) {
			throw new Meteor.Error('error-duplicate-role-names-not-allowed', 'Role name already exists');
		}

		const roleData = {
			description: description || '',
			...(mandatory2fa !== undefined && { mandatory2fa }),
			name,
			scope: scope || 'Users',
			protected: false,
		};

		const options = {
			broadcastUpdate: settings.get<boolean>('UI_DisplayRoles'),
		};

		const role = await insertRoleAsync(roleData, options);

		return API.v1.success({ role });
	},

View on GitHub (pinned to b2c16d5842)