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
- Check Roles.findOneByName(name) first and skip / update instead of insert.
- Catch MeteorError code 'error-duplicate-role-names-not-allowed' and treat as idempotent.
- 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
- Make role-seeding scripts idempotent.
- Pre-check name uniqueness in the role form.
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
- error-duplicate-role-names-not-allowed
- error-invalid-scope
- error-invalid-roleId
- error-role-protected
- error-invalid-scope
AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12).
Data as JSON: /api/errors/a14be4c13dca2180.
Report an issue: GitHub.