RocketChat/Rocket.Chat · warning · Meteor.Error

error-role-protected

error-role-protected

Error message

Cannot delete a protected role

What it means

Thrown by POST roles.delete when the resolved role has role.protected === true. Protected roles (e.g. admin, owner, moderator, user) are built-in and cannot be removed. Returns a structured Meteor.Error('error-role-protected', 'Cannot delete a protected role').

Source

Thrown at apps/meteor/server/api/v1/roles.ts:243

					required: ['success'],
					additionalProperties: false,
				}),
				400: validateBadRequestErrorResponse,
				401: validateUnauthorizedErrorResponse,
				403: validateForbiddenErrorResponse,
			},
		},
		async function action() {
			const { bodyParams } = this;

			const role = await Roles.findOneByIdOrName(bodyParams.roleId);

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

			if (role.protected) {
				throw new Meteor.Error('error-role-protected', 'Cannot delete a protected role');
			}

			if ((await Roles.countUsersInRole(role._id)) > 0) {
				throw new Meteor.Error('error-role-in-use', "Cannot delete role because it's in use");
			}

			await Roles.removeById(role._id);

			void notifyOnRoleChanged(role, 'removed');

			return API.v1.success();
		},
	)
	.post(
		'roles.removeUserFromRole',
		{
			authRequired: true,
			permissionsRequired: ['access-permissions'],

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Do not delete protected roles; instead remove users from the role or adjust permissions if you need to change behavior.
  2. Filter the role list by protected===false before offering a delete action in the UI.
  3. Create a custom non-protected role if you need a deletable equivalent.

Example fix

// before
await fetch('/api/v1/roles.delete', { method:'POST', body: JSON.stringify({ roleId }) });

// after - block protected roles in the UI
const role = roles.find(r => r._id === roleId);
if (role?.protected) {
  alert('Protected roles cannot be deleted');
  return;
}
await fetch('/api/v1/roles.delete', { method:'POST', body: JSON.stringify({ roleId }) });
Defensive patterns

Strategy: type-guard

Validate before calling

const role = roles.find(r => r._id === roleId);
if (!role || role.protected) {
  throw new Error('Protected or unknown roles cannot be deleted');
}
await fetch('/api/v1/roles.delete', { method:'POST', body: JSON.stringify({ roleId }) });

Type guard

function isDeletableRole(role: { protected?: boolean } | undefined): role is { protected: false } {
  return !!role && role.protected !== true;
}

Prevention

When it happens

Trigger: POST /api/v1/roles.delete targeting a built-in protected role whose documents has protected:true.

Common situations: Admin UI/script attempts to remove a system role to simplify permissions; migration tries to drop the 'admin' or 'user' role.

Related errors


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