RocketChat/Rocket.Chat · warning · Meteor.Error

error-action-not-allowed

error-action-not-allowed

Error message

This is an enterprise feature

What it means

Thrown by POST roles.create when the server license does not include the 'custom-roles' module. Custom (user-defined) roles are an Enterprise feature; the route also declares license: ['custom-roles'] at the route level, so this in-action check is a redundant safety net in case the license middleware is bypassed or the route-level gate is later removed. Returns Meteor.Error error-action-not-allowed.

Source

Thrown at apps/meteor/ee/server/api/roles.ts:115

	required: ['role', 'success'],
	additionalProperties: false,
});

API.v1.post(
	'roles.create',
	{
		authRequired: true,
		license: ['custom-roles'],
		body: isRoleCreateProps,
		response: {
			200: roleResponseSchema,
			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,

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Apply an Enterprise license that includes the custom-roles module.
  2. If you cannot get custom-roles, use only the built-in roles instead of creating new ones.
  3. Restore the route-level license: ['custom-roles'] gate if it was removed, so the middleware rejects pre-handler.
  4. Confirm the license module list via the license/workspace API.

Example fix

// before: route handler reached without a license gate
API.v1.post('roles.create', { authRequired: true }, action);

// after: declare the license requirement at route level so middleware rejects cleanly
API.v1.post('roles.create', { authRequired: true, license: ['custom-roles'], body: isRoleCreateProps }, action);
Defensive patterns

Strategy: validation

Validate before calling

// Gate custom-role creation on the license module
async function canManageCustomRoles(): Promise<boolean> {
  const { license } = await (await fetch('/api/v1/license.get', { headers: authHeaders() })).json();
  return Array.isArray(license?.modules) && license.modules.includes('custom-roles');
}
if (!(await canManageCustomRoles())) {
  throw new Error('custom-roles module not licensed');
}

Type guard

function hasCustomRolesLicense(modules: unknown): modules is string[] {
  return Array.isArray(modules) && modules.includes('custom-roles');
}

Try / catch

try {
  await api.post('roles.create', body);
} catch (e) {
  if (isMeteorError(e, 'error-action-not-allowed') && /enterprise/i.test(e.reason)) {
    showUpgradePrompt('custom-roles');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling POST /v1/roles.create on Community edition or on an Enterprise license without custom-roles. Normally the route-level license middleware rejects earlier, so seeing this exact throw means the middleware gate was removed or the action was invoked directly.

Common situations: CE deployment; expired Enterprise license; EE license missing the custom-roles add-on; refactored route that dropped the license: [] gate.

Related errors


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