RocketChat/Rocket.Chat · error · MeteorError

error-invalid-scope

error-invalid-scope

Error message

Invalid scope

What it means

insertRoleAsync validates the scope with isValidRoleScope (apps/meteor/lib/roles/isValidRoleScope.ts), which accepts exactly the strings 'Users' and 'Subscriptions'. Anything else - 'Global', 'global', 'users', undefined - throws MeteorError('error-invalid-scope', 'Invalid scope'). Scope determines whether the role is assigned to users or to room subscriptions, so only those two literals are meaningful.

Solutions

  1. Set scope to exactly 'Users' or 'Subscriptions' depending on whether the role applies to users or subscriptions.
  2. Enforce the enum at the API boundary (e.g. ajv enum schema) so invalid scopes fail as 400 input errors, not server errors.
  3. When unsure, omit nothing: pick 'Users' for normal user roles - most custom roles use it.

Example fix

// before
await insertRoleAsync({ name: 'auditor', scope: 'Global', description: 'x' }); // throws error-invalid-scope

// after
await insertRoleAsync({ name: 'auditor', scope: 'Users', description: 'x' });
Defensive patterns

Strategy: validation

Validate before calling

const isRoleScope = (scope: unknown): boolean => scope === 'Users' || scope === 'Subscriptions';
if (!isRoleScope(scope)) {
	// reject the input before calling insertRoleAsync
}

Type guard

type RoleScope = 'Users' | 'Subscriptions';
const isRoleScope = (scope: unknown): scope is RoleScope => scope === 'Users' || scope === 'Subscriptions';

Try / catch

try {
	await insertRoleAsync(roleData);
} catch (e: any) {
	if (e?.error === 'error-invalid-scope') throw new Meteor.Error(400, "scope must be 'Users' or 'Subscriptions'");
	throw e;
}

Prevention

When it happens

Trigger: Calling insertRoleAsync with a scope outside ['Users', 'Subscriptions'] - case mismatches ('users'), legacy/free-text values ('Global', 'Room'), or the field omitted entirely when the caller assumed a default.

Common situations: Scripts ported from other systems that used different scope vocabulary; case-sensitive payloads built by hand; schemas that do not enforce the enum letting arbitrary strings through to the server.

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/91193bfbce2ef73a. Report an issue: GitHub.

Appendix: source

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

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