Skip to main content
Your bot can react to any server message it can see, remove its own reaction, and hear when reactions change anywhere it is watching. Reactions are keyed by an opaque reactionId: a unicode emoji for the common case, or a custom emoji id for server-specific emoji. This page covers both sides, adding reactions and listening for them.
Reactions do not work on direct messages. react() and unreact() reject with a plain Error on any message where msg.isDM is true, regardless of permissions, before a frame is built. Direct messages arrive on the same messageCreate event as server messages, so guard every handler.

Add a reaction

Call msg.react(reactionId) on a Message to add the bot’s reaction. It returns Promise<void> and requires the reaction_add permission. For a plain emoji, pass the unicode character directly.
The if (msg.isDM || !msg.serverId) return; guard does two jobs. It keeps react() away from DM messages, and it narrows msg.serverId from string | null to string so can() accepts it. Without it, TypeScript rejects the can() call and plain JavaScript silently returns false for a null server. The reactionId is opaque. The SDK does not validate it and passes it straight through to the server. An invalid id is refused by the server, not caught locally.

Remove the bot’s reaction

Call msg.unreact(reactionId) to remove the bot’s own reaction from a message. It only affects the reaction the bot added, not anyone else’s.

React with a custom emoji

For a server’s custom emoji, pass its id instead of a unicode character. Get ids from client.fetchEmojis(), which returns Promise<{ emojis, category }>, the global emoji catalog. Its ids are exactly the custom-emoji ids that react() accepts.
The shape of emojis and category is defined by the server, so treat them as data to look ids up in rather than a fixed structure.

Listen for reaction changes

The reactionUpdate event fires when a reaction is added or removed on a message your bot can see. It is not scoped to a channel you selected: on a current Cloak server it arrives for every server the bot is in, the same way messageCreate does. Filter on channelId if you care about one place.
string
The message whose reactions changed.
string
The reaction that changed, the same opaque id you pass to react().
number
The new total for that reaction on the message. The event does not say whether the change was an add or a remove, only the new count.
string
The user who added or removed the reaction.
string
The server the message belongs to.
string
The channel the message belongs to.
Because count is the new total and no add-or-remove flag is included, treat the event as “this reaction now has this many”, not “someone just added one”.

Filter out the bot’s own reactions

reactionUpdate fires for the bot’s own reactions too, including the ones it adds with react(). When that matters, filter on userId.
On an older Cloak server, serverId and channelId on reactionUpdate can fall back to the bot’s currently selected server and channel. An up-to-date Cloak server always reports the message’s own location.

React to being mentioned

A reaction acknowledges something without adding a message to the channel, which makes it a good pairing with msg.mentionsMe.
See Mentions and replies for what mentionsMe does and does not cover.

Handling errors

Three different failures are worth telling apart.
react() and unreact() reject with a plain Error in two cases. On a direct message, unconditionally. And when the message has no createdAt, which the server needs to locate the row. Live messages from messageCreate and messages loaded with fetchMessages both carry createdAt, so in practice only the DM case comes up.
Both methods reject with CloakActionError when the server refuses. Read .code for the reason and .permission for the missing permission when the code maps to one.
Confirming a reaction needs a Cloak server new enough to send the bot a success acknowledgement. Against an older backend the call cannot confirm and times out even though the reaction landed. See What the server must support.
Pre-gate with can('reaction_add', serverId) to skip actions the server would only refuse, and still catch the promise for the cases you cannot predict.

Next

Editing, deleting, and pinning

The other four message actions, under the same DM rule.

The Message object

Every field and method a received message carries.

Handling errors

Working with CloakActionError and friends.

Permissions

Why reaction_add must be granted first.