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
- Set scope to exactly 'Users' or 'Subscriptions' depending on whether the role applies to users or subscriptions.
- Enforce the enum at the API boundary (e.g. ajv enum schema) so invalid scopes fail as 400 input errors, not server errors.
- 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
- Model scope as a closed union type ('Users' | 'Subscriptions') in payloads and forms.
- Enforce the enum at the API boundary with a JSON-schema validator.
- Watch for case and whitespace drift when scope values come from user input.
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
- error-invalid-scope
- error-action-not-allowed
- error-duplicate-role-names-not-allowed
- error-duplicate-role-names-not-allowed
- error-invalid-contact-manager
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)