Platform-agnostic emoji → shortcode resolver.
Built for the case where an LLM hands you something emoji-ish and you need a real reaction out the other side. Works with Slack, Discord, and anything that speaks shortcodes or unicode.
npm install emoji-resolveimport {
resolve,
toShortcode,
toName,
registerCustom,
} from "emoji-resolve"
import { Slack } from "emoji-resolve/platforms/slack"
import { Discord } from "emoji-resolve/platforms/discord"
// Workspace custom emoji (once at bot startup)
registerCustom({
shipit: "https://emoji.slack-edge.com/T…/shipit.png",
partyparrot: "https://emoji.slack-edge.com/T…/partyparrot.gif",
meow: "alias:partyparrot",
})
// Or from Slack's API:
// registerCustom(await client.emoji.list())
toShortcode("😂") // ":joy:"
toShortcode("laugh") // ":joy:"
toShortcode("thums up") // ":+1:" typo-tolerant
toShortcode("ship it") // ":shipit:" workspace custom
toName("🎉") // "tada"
const hit = resolve("party parrot")!
// { name: "partyparrot", shortcode: ":partyparrot:", custom: true, … }
Slack.reaction(hit) // "partyparrot" → reactions.add
Discord.reaction(hit) // "partyparrot" (add id for name:id — see below)Zero runtime dependencies. Plain TypeScript functions — no framework required.
npm install emoji-resolve
# or
pnpm add emoji-resolve
# or
yarn add emoji-resolvePublished on npm · source on GitHub
A typical Slack bot startup + reaction flow:
import { WebClient } from "@slack/web-api"
import { toName, registerCustom, resolve } from "emoji-resolve"
import { Slack } from "emoji-resolve/platforms/slack"
const client = new WebClient(process.env.SLACK_BOT_TOKEN)
// 1. Load workspace emoji once
const { emoji } = await client.emoji.list()
registerCustom(emoji)
// 2. LLM (or user) said something emoji-ish
const input = "ship it" // or "🎉", "thums up", "party parrot", …
// 3. React
const name = toName(input) // "shipit" | "tada" | "+1" | …
await client.reactions.add({
channel,
timestamp: messageTs,
name, // bare name — what reactions.add wants
})
// Full match when you need score / shortcode / custom url:
const hit = resolve(input)
if (hit) {
console.log(hit.shortcode, hit.score, hit.custom, hit.url)
// or: Slack.reaction(hit) / Slack.shortcode(hit)
}Discord custom emoji need a snowflake id:
import { registerCustom, resolve } from "emoji-resolve"
import { Discord } from "emoji-resolve/platforms/discord"
registerCustom([
{ name: "partyparrot", id: "123456789012345678", animated: true },
{ name: "shipit", id: "987654321098765432" },
])
const hit = resolve("party parrot")!
Discord.reaction(hit) // "partyparrot:123456789012345678"
Discord.message(hit) // "<a:partyparrot:123456789012345678>"Chatbots and agents get emoji input in every shape:
| Input | Example |
|---|---|
| Raw unicode | 🎉 👍🏽 |
| Shortcode | :tada: tada |
| Loose word | laugh, party |
| Typo | thums up |
| CLDR description | party popper, face with tears of joy |
| Workspace custom | shipit, partyparrot |
One library turns all of that into something platforms accept.
| Entry | Purpose |
|---|---|
emoji-resolve |
Converters: resolve, toShortcode, toName, suggest, registerCustom, listCustom |
emoji-resolve/formatters |
String transforms: textToShortcode, toUnicode, extractEmoji |
emoji-resolve/platforms/slack |
Slack reaction + shortcode helpers |
emoji-resolve/platforms/discord |
Discord reaction + message helpers |
emoji-resolve/data |
Raw builtin maps |
Default is converters. Formatters and platforms are opt-in.
import {
resolve,
toShortcode,
toName,
suggest,
registerCustom,
listCustom,
} from "emoji-resolve"
toShortcode("😂") // ":joy:"
toShortcode("xqzztplorb") // ":question:"
toShortcode("xqzztplorb", { fallback: ":wave:" })
toName("🎉") // "tada"
toName("xqzztplorb") // "question"
resolve("thums up") // Match | null
suggest("party", 3) // Match[]| Function | Returns | Notes |
|---|---|---|
resolve(input, opts?) |
Match | null |
Full match, or null if nothing is confident |
toShortcode(input, opts?) |
string |
Always a shortcode; default fallback ":question:" |
toName(input, opts?) |
string |
Bare name (no colons); default fallback "question" |
suggest(input, n?, opts?) |
Match[] |
Top-N candidates |
registerCustom(emoji) |
number |
Install workspace emoji; null to clear |
listCustom() |
string[] |
Currently installed custom names |
Back-compat aliases: toSlack → toShortcode, reactionName → toName.
type Match = {
name: string // "tada", "+1"
shortcode: string // ":tada:", ":+1::skin-tone-4:"
emoji: string | null // "🎉" (null for workspace-only custom)
score: number // 0–1
tone?: string // "skin-tone-4" when present
custom?: boolean
url?: string | null
id?: string | null // Discord snowflake when registered
animated?: boolean
}Canonical names follow the common Slack/GitHub shortcode set (+1, tada, joy, …).
type ResolveOptions = {
customEmoji?: unknown // workspace emoji for this call only
minScore?: number // default 0.5
fallback?: string // for toShortcode / toName / platform *From helpers
}import { Slack } from "emoji-resolve/platforms/slack"
const hit = resolve("🎉")!
Slack.reaction(hit) // "tada" → reactions.add
Slack.shortcode(hit) // ":tada:" → message text
Slack.reactionFrom("thums up") // "+1"
Slack.shortcodeFrom("laugh") // ":joy:"Standard reactions want unicode. Custom reactions want name:id.
import { Discord } from "emoji-resolve/platforms/discord"
import { registerCustom, resolve } from "emoji-resolve"
const hit = resolve("🎉")!
Discord.reaction(hit) // "🎉"
Discord.message(hit) // "🎉"
registerCustom([
{ name: "partyparrot", id: "1234567890", animated: true },
])
const custom = resolve("partyparrot")!
Discord.reaction(custom) // "partyparrot:1234567890"
Discord.message(custom) // "<a:partyparrot:1234567890>"| Need | Use |
|---|---|
| Slack reaction | Slack.reaction(match) or toName(input) |
| Slack message shortcode | Slack.shortcode(match) or toShortcode(input) |
| Discord standard reaction | Discord.reaction(match) → unicode |
| Discord custom reaction | register with { name, id }, then Discord.reaction |
| Discord message fragment | Discord.message(match) |
import {
textToShortcode,
toUnicode,
extractEmoji,
} from "emoji-resolve/formatters"
textToShortcode("hello 🎉 world 😂")
// "hello :tada: world :joy:"
toUnicode("joy") // "😂"
toUnicode(":tada:") // "🎉"
toUnicode("thums up") // "👍"
extractEmoji("a 🎉 b 👍🏽 c")
// ["🎉", "👍🏽"]| Function | What it does |
|---|---|
textToShortcode(text) |
Replace every emoji character with its shortcode (no fuzzy words) |
toUnicode(input, opts?) |
Shortcode / name / word → unicode |
extractEmoji(text) |
Pull graphemes (skin tones, ZWJ) out of text |
import { registerCustom, resolve, listCustom } from "emoji-resolve"
// Slack emoji.list()
registerCustom(await client.emoji.list())
// Name → URL map
registerCustom({
shipit: "https://emoji.slack-edge.com/…/shipit.png",
meow: "alias:partyparrot",
})
// Names only
registerCustom(["shipit", "partyparrot"])
// Discord-friendly (with snowflake ids)
registerCustom([
{ name: "partyparrot", id: "1234567890", animated: true },
])
// Per-call, without installing globally
resolve("shipit", { customEmoji: { shipit: "https://…" } })
listCustom() // ["shipit", …]
registerCustom(null) // clearWorkspace matches prefer custom names over builtins when confident (ship it → :shipit: not :rocket:).
- Literal emoji in the input → exact name (+ skin tone when present)
- Shortcode / plain name against workspace custom, then builtins
- Fuzzy for queries ≥ 3 chars: BM25 over aliases + substring + edit distance
- Short noise (1–2 chars that miss exact) does not guess
Concept aliases widen matching (party → tada, lol → joy, lgtm → +1, CLDR names → shortcode names, …).
Popular reactions get a small tie-break boost so bots land on what humans actually click.
import { EMOJI_TO_NAME, NAME_TO_EMOJI } from "emoji-resolve/data"
EMOJI_TO_NAME.get("😂") // "joy"
NAME_TO_EMOJI.get("joy") // "😂"Aliases EMOJI_TO_SLACK / SLACK_TO_EMOJI still exist for older imports.
git clone https://github.com/supermemoryai/emoji-resolve.git
cd emoji-resolve
npm ci
npm test # build + vitestCI runs on every push and PR to main (Node 20 & 22).
MIT · Supermemory