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 insertRoleAsync in insertRole.ts:16 when Roles.findOneByName(name) returns a role — i.e. a role with that name already exists. Uses MeteorError with code 'error-duplicate-role-names-not-allowed'. Role names are globally unique in Rocket.Chat.

Source

Thrown at apps/meteor/ee/server/lib/roles/insertRole.ts:16

import { api, MeteorError } from '@rocket.chat/core-services';
import type { IRole } from '@rocket.chat/core-typings';
import { Roles } from '@rocket.chat/models';

import { isValidRoleScope } from '../../../../lib/roles/isValidRoleScope';
import { notifyOnRoleChanged } from '../../../../server/lib/notifyListener';

type InsertRoleOptions = {
	broadcastUpdate?: boolean;
};

export const insertRoleAsync = async (roleData: Omit<IRole, '_id' | '_updatedAt'>, options: InsertRoleOptions = {}): Promise<IRole> => {
	const { name, scope, description, mandatory2fa } = roleData;

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

	if (!isValidRoleScope(scope)) {
		throw new MeteorError('error-invalid-scope', 'Invalid scope');
	}

	const role = await Roles.createWithRandomId(name, scope, description, false, mandatory2fa);

	void notifyOnRoleChanged(role);

	if (options.broadcastUpdate) {
		void api.broadcast('user.roleUpdate', {
			type: 'changed',
			_id: role._id,
		});
	}

	return role;

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Check Roles.findOneByName(name) first and skip / update instead of insert.
  2. Catch MeteorError code 'error-duplicate-role-names-not-allowed' and treat as idempotent.
  3. Use a unique name or call updateRole on the existing _id.

Example fix

// before
await insertRoleAsync({ name, scope, description });

// after
if (await Roles.findOneByName(name)) {
  // already exists; treat as success or update
  return Roles.findOneByName(name);
}
await insertRoleAsync({ name, scope, description });
Defensive patterns

Strategy: validation

Validate before calling

if (await Roles.findOneByName(name)) { /* skip or update instead */ return; }

Type guard

const isRoleNameFree = async (name: string) => !(await Roles.findOneByName(name));

Try / catch

try { await insertRoleAsync(roleData); }
catch (e) {
  if (e?.code === 'error-duplicate-role-names-not-allowed') return;
  throw e;
}

Prevention

When it happens

Trigger: Calling insertRoleAsync with a name that matches any existing role (case-sensitive findOneByName). Happens on role creation via UI, REST, or seeding scripts.

Common situations: Re-running a setup/seeding script; admin manually creates a role that ships as default; migration recreates built-in roles.

Related errors


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