RocketChat/Rocket.Chat · error
Invalid reaction
Error message
Invalid reaction
What it means
Thrown by the Apps-Engine server bridge AppMessagesBridge.addReaction when an app adds a reaction whose string fails isValidReaction: it must both start and end with ':' (e.g. ':smile:'). Rocket.Chat keys reactions by colon-wrapped emoji shortnames, so the bridge rejects any other shape before calling executeSetReaction.
Solutions
- Pass the colon-wrapped shortname: ':smile:' instead of 'smile'
- Normalize before calling: `reaction = ':' + name.trim().replace(/^:+|:+$/g, '') + ':'`
- If the value comes from end users, validate it against your emoji set before calling react
- Do not pass unicode emoji characters or image URLs — only shortnames
Example fix
// before await app.getRocketChat().getChat().reactMessage(message, 'smile'); // throws Invalid reaction // after await app.getRocketChat().getChat().reactMessage(message, ':smile:');
Defensive patterns
Strategy: validation
Validate before calling
const toReaction = (name: string): string => `:${name.trim().replace(/^:+|:+$/g, '')}:`;
const reaction = toReaction('smile'); // ':smile:'
if (reaction.length < 3) throw new Error('empty reaction name');
await app.getRocketChat().getChat().reactMessage(message, reaction); Type guard
import type { Reaction } from '@rocket.chat/apps-engine/definition/messages';
const isReactionFormat = (r: string): r is Reaction =>
r.startsWith(':') && r.endsWith(':') && r.trim().length >= 3;
if (!isReactionFormat(reaction)) reaction = toReaction(reaction); Try / catch
try {
await app.getRocketChat().getChat().reactMessage(message, reaction);
} catch (err) {
if (err instanceof Error && err.message === 'Invalid reaction') {
// normalize once and retry with the wrapped shortname
await app.getRocketChat().getChat().reactMessage(message, toReaction(reaction));
} else {
throw err;
}
} Prevention
- Centralize reaction formatting in one helper used by add and remove paths
- Store/accept only shortnames in app settings and UIs, never raw unicode emoji
- Trim user input before wrapping in colons
- Unit-test the helper against ':smile', 'smile:', '', and '👍'
When it happens
Trigger: An app calls the react API with 'smile' (no colons), ':smile' (missing trailing colon), a raw unicode emoji like '👍', an emoji image URL, or an empty/undefined reaction; the guard reaction.startsWith(':') && reaction.endsWith(':') fails and the Error propagates to the app.
Common situations: Apps building the reaction from slash-command params, settings, or user input without wrapping it in colons; porting code that used raw emoji characters; values with stray whitespace (':smile: ') that break the endsWith check.
Related errors
- Invalid description
- Invalid message id
- Invalid user
- Invalid user id
- Missing user to perform the upload operation
AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18).
Data as JSON: /api/errors/d9e495f3d353dc06.
Report an issue: GitHub.
Appendix: source
Thrown at apps/meteor/app/apps/server/bridges/messages.ts:126
case 'room':
if (!username) {
throw new Error('Invalid username');
}
notifications.notifyRoom(id, 'user-activity', username, isTyping ? ['user-typing'] : []);
return;
default:
throw new Error('Unrecognized typing scope provided');
}
}
private isValidReaction(reaction: Reaction): boolean {
return reaction.startsWith(':') && reaction.endsWith(':');
}
protected async addReaction(messageId: string, userId: string, reaction: Reaction): Promise<void> {
if (!this.isValidReaction(reaction)) {
throw new Error('Invalid reaction');
}
return executeSetReaction(userId, reaction, messageId, true);
}
protected async removeReaction(messageId: string, userId: string, reaction: Reaction): Promise<void> {
if (!this.isValidReaction(reaction)) {
throw new Error('Invalid reaction');
}
return executeSetReaction(userId, reaction, messageId, false);
}
}
View on GitHub (pinned to b2c16d5842)