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

Thrown by POST roles.create when Roles.findOneByIdOrName(name) returns an existing role. Role names (and IDs) must be unique; passing a name that collides with an existing role (by _id or by name) is rejected. Returns Meteor.Error error-duplicate-role-names-not-allowed.

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 f9d3ec372b)

Solutions

  1. Check for an existing role via GET /v1/roles.list before creating, and reuse it if present.
  2. Choose a unique role name.
  3. If the duplicate was accidental, delete the existing role first (DELETE /v1/roles.delete) if it is not protected.
  4. Make setup scripts idempotent: skip creation when the role already exists.

Example fix

// before: unconditional create
await POST('/v1/roles.create', { name: 'support-agent' });

// after: idempotent create
const existing = (await GET('/v1/roles.list')).roles.find(r => r.name === 'support-agent');
if (!existing) {
  await POST('/v1/roles.create', { name: 'support-agent' });
}
Defensive patterns

Strategy: validation

Validate before calling

// Idempotency check before create
const { roles } = await api.get('roles.list');
if (roles.some(r => r.name === body.name || r._id === body.name)) {
  throw new Error(`Role '${body.name}' already exists`);
}
await api.post('roles.create', body);

Try / catch

try {
  await api.post('roles.create', body);
} catch (e) {
  if (isMeteorError(e, 'error-duplicate-role-names-not-allowed')) {
    // role already exists; fetch and reuse it
    return api.get('roles.list').then(r => r.roles.find(x => x.name === body.name));
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling POST /v1/roles.create with a body whose name matches an existing role's name or _id. Happens on retry after a partial create, on duplicate submissions, or when re-importing a role set.

Common situations: Re-running a setup script that creates roles; name collision with a built-in role (admin, user, livechat-agent); case sensitivity surprises (lookup is by DB exact match).

Related errors


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