Slack reactions.add API: Names, Errors and the 23-Reaction Limit
We called reactions.add with standard, aliased, skin-tone and custom emoji names in a test workspace, then kept adding reactions until Slack refused. Every response, the reaction_added payload, and the per-user limit we hit.
On this page
To add a reaction with the Slack API, call reactions.add with the message's channel, its timestamp (the message ts), and the emoji name without colons, using a token with the reactions:write scope. We tested it on 1 October 2026 in our own test workspace with a bot token and a user token. Each app or person can put 23 different reactions on one message; the 24th returns too_many_reactions.
Add a reaction
This is the script we ran against a message our bot had posted:
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
CHANNEL, TS = "C0C5QG3TQKV", "1790838915.131049"
for name in ["thumbsup", "thumbsup", "+1", ":eyes:", "wave::skin-tone-3", "squirrel", "shipit"]:
try:
client.reactions_add(channel=CHANNEL, timestamp=TS, name=name)
print(f"{name:20} ok")
except SlackApiError as e:
print(f"{name:20} {e.response['error']}")
msg = client.reactions_get(channel=CHANNEL, timestamp=TS)["message"]
print([r["name"] for r in msg["reactions"]])
Output:
thumbsup ok
thumbsup already_reacted
+1 already_reacted
:eyes: invalid_name
wave::skin-tone-3 ok
squirrel ok
shipit already_reacted
['umbrella', '+1', 'squirrel', 'wave::skin-tone-3']
What the output shows:
- •
thumbsupand+1are the same emoji. Afterthumbsup, adding+1returnedalready_reacted, andreactions.getlists the reaction as+1. - • A name in colons,
:eyes:, isinvalid_name. Strip the colons first. - • A skin tone goes after a double colon:
wave::skin-tone-3. The single-colon formwave:skin-tone-3returnedinvalid_name. Our Slack skin tone page lists the six tone numbers. - • Custom emoji work by name.
shipitis an alias ofsquirrelin this workspace (emoji.listreturnsalias:squirrelfor it), so it was already there. Before you react with a custom emoji, upload it; the Slack emoji maker makes one at the right size.
Names are case-sensitive in practice: Eyes returned invalid_name, eyes worked.
Errors we got
| What we sent | Response |
|---|---|
| A reaction the same token already added | {"ok": false, "error": "already_reacted"} |
not_a_real_emoji_xyz, :eyes:, Eyes | {"ok": false, "error": "invalid_name"} |
A ts that does not exist in the channel | {"ok": false, "error": "message_not_found"} |
| A public channel the bot has not joined (bot token) | {"ok": false, "error": "not_in_channel"} |
| A channel ID that does not exist | {"ok": false, "error": "channel_not_found"} |
reactions.remove for a reaction the token never added | {"ok": false, "error": "no_reaction"} |
| The 24th different reaction from one token | {"ok": false, "error": "too_many_reactions"} |
The not_in_channel row only applies to the bot. In the same channel, the user token's reactions.add returned ok: true, because the user was a member. The fix for the bot is conversations.join or an invite; not_in_channel covers both.
Never appear "away" on Slack again
Cloud-based. No downloads. Works 24/7 even when your laptop is off.
The 23-reaction limit is per person
Our bot added different emoji one at a time. The 24th, umbrella, returned too_many_reactions. Then our user token added umbrella to the same message, and it worked. The user went on until its own 24th different emoji was refused. The message ended with 44 different reactions: 23 from the user and 22 from the bot, with umbrella from both. That count is after we removed two of the bot's reactions:
So the limit counts different emoji per reacting user or bot, not per message. It matches what a person sees in the Slack app, where the emoji picker stops at 23 too, as in our eyes emoji test. After reactions.remove took one of the bot's reactions off, the bot could add a new one again. Removing a reaction that both the bot and the user had left took off only the bot's; the user's stayed with count 1.
The reaction_added event
With reaction_added in the app's bot events and reactions:read in its scopes, Slack sent one event per reaction, including the ones our own bot added. This is the event for the bot's thumbsup:
{
"type": "reaction_added",
"user": "U0C5WG3PXHQ",
"reaction": "+1",
"item": {
"type": "message",
"channel": "C0C5QG3TQKV",
"ts": "1790838871.694209",
"channel_type": "channel"
},
"item_user": "U0C5WG3PXHQ",
"event_ts": "1790838872.000300"
}
The reaction field holds the stored name, +1, not the thumbsup we sent. If your code matches reaction names, compare against the stored names. Skin tones arrive as wave::skin-tone-3. Because your own bot's reactions come back as events, skip events whose user is your bot's user ID, or a bot that answers every reaction with a reaction will trigger itself.
FAQ
Can a bot react to a message in a DM?
Yes. Our bot added eyes to a message the user had sent in the bot's own DM, with reactions:write and the DM's D channel ID, and got ok: true.
How do I find which reactions a message has?
Call reactions.get with the channel and timestamp. It returns each name with its count and the users who added it. To find messages by reaction in the Slack app, use has:: search.
Stop Jiggling Your Mouse.
Join hundreds of remote workers who never worry about their Slack status. Set it up once, stay green forever.
Related Articles
Slack Interactivity Payload: Real block_actions JSON and response_url Limits
We clicked a button, a static_select and a datepicker in Slack on 1 October 2026 and logged the block_actions payloads our app received. Then we measured response_url: 5 posts, then used_url; it worked at 29:50 and was dead at 30:10.
Slack unfurl_links and unfurl_media: What Each Flag Does, Tested
We posted a web page, a YouTube video and an image with every combination of unfurl_links and unfurl_media on 1 October 2026, then built a custom preview with link_shared and chat.unfurl. The flags do not split the way the names suggest.
Slack Scheduled Message Didn't Send? 5 Cases We Tested
We scheduled five messages for 4:40 PM on 1 October 2026, then archived, left and deleted their channels and deleted a thread parent before the send time. Two sent, three never did, and Slack gave no notice about the three.