RocketChat/Rocket.Chat · error · MeteorError

error-duplicate-role-names-not-allowed

error-duplicate-role-names-not-allowed

Error message

Role name already exists

What it means

Thrown by updateRole in updateRole.ts:30 during a rename: another role already uses the requested name. findOneByName excludes nothing, so the code manually compares otherRole._id !== role._id. MeteorError code 'error-duplicate-role-names-not-allowed'.

Source

Thrown at apps/meteor/ee/server/lib/roles/updateRole.ts:30

export const updateRole = async (
	roleId: IRole['_id'],
	roleData: Omit<IRole, '_id' | '_updatedAt'>,
	options: UpdateRoleOptions = {},
): Promise<IRole> => {
	const role = await Roles.findOneById(roleId);

	if (!role) {
		throw new MeteorError('error-invalid-roleId', 'This role does not exist');
	}

	if (role.protected && ((roleData.name && roleData.name !== role.name) || (roleData.scope && roleData.scope !== role.scope))) {
		throw new MeteorError('error-role-protected', 'Role is protected');
	}

	if (roleData.name) {
		const otherRole = await Roles.findOneByName(roleData.name, { projection: { _id: 1 } });
		if (otherRole && otherRole._id !== role._id) {
			throw new MeteorError('error-duplicate-role-names-not-allowed', 'Role name already exists');
		}
	} else {
		roleData.name = role.name;
	}

	if (roleData.scope) {
		if (!isValidRoleScope(roleData.scope)) {
			throw new MeteorError('error-invalid-scope', 'Invalid scope');
		}
	} else {
		roleData.scope = role.scope;
	}

	await Roles.updateById(roleId, roleData.name, roleData.scope, roleData.description, roleData.mandatory2fa);

	void notifyOnRoleChangedById(roleId);

	if (options.broadcastUpdate) {

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Pick a role name not used by any other role.
  2. Pre-check Roles.findOneByName(roleData.name) and ensure _id matches the role being edited.
  3. Catch MeteorError 'error-duplicate-role-names-not-allowed' and prompt for a unique name.

Example fix

// before
await updateRole(roleId, { name: newName });

// after
const clash = await Roles.findOneByName(newName, { projection: { _id: 1 } });
if (clash && clash._id !== roleId) {
  throw new Error('name taken');
}
await updateRole(roleId, { name: newName });
Defensive patterns

Strategy: validation

Validate before calling

if (roleData.name) {
  const clash = await Roles.findOneByName(roleData.name, { projection: { _id: 1 } });
  if (clash && clash._id !== roleId) throw new Error('name taken');
}

Type guard

const isRoleNameFreeFor = async (name: string, selfId: string) => {
  const c = await Roles.findOneByName(name, { projection: { _id: 1 } });
  return !c || c._id === selfId;
};

Try / catch

try { await updateRole(roleId, { name: newName }); }
catch (e) {
  if (e?.code === 'error-duplicate-role-names-not-allowed') { /* pick unique name */ return; }
  throw e;
}

Prevention

When it happens

Trigger: Renaming role A to the name of role B; or submitting the form with a name collision. Setting roleData.name to its own current name does not fire (different _id check passes).

Common situations: Admin tries to rename a role to something already in use; import script normalizing names to existing ones.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/89989edf1cab4326. Report an issue: GitHub.