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

  1. Pass the colon-wrapped shortname: ':smile:' instead of 'smile'
  2. Normalize before calling: `reaction = ':' + name.trim().replace(/^:+|:+$/g, '') + ':'`
  3. If the value comes from end users, validate it against your emoji set before calling react
  4. 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

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


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)