Resolvers and mentions
Slack payloads reference workspace entities by ID: <@U024BE7LH> in mrkdwn,
{ type: "user", user_id: "U024BE7LH" } in rich text. The renderer has no
workspace access, so by default it shows the ID (or the payload's own fallback
label). The resolvers prop maps IDs to display values without changing the
payload:
import { BlockKit, type BlockKitInput } from "react-blockkit";
const users: Record<string, string> = { U024BE7LH: "Maya Ito" };
const channels: Record<string, string> = { C024BE91L: "release-train" };
const groups: Record<string, string> = { S0614TZR7: "on-call" };
export function Preview({ data }: { data: BlockKitInput }) {
return (
<BlockKit
data={data}
resolvers={{
user: (id) => users[id],
channel: (id) => channels[id],
usergroup: (id) => groups[id],
}}
/>
);
}Resolvers apply to mentions in both mrkdwn text and rich_text blocks, and
may return any React node: a styled chip, an avatar and name, plain text.
Returning undefined falls through to the default rendering for that mention.
Available resolvers
| Resolver | Signature | Resolves |
|---|---|---|
user | (id: string) => ReactNode | undefined | <@U…> and rich-text user elements |
channel | (id: string) => ReactNode | undefined | <#C…> and rich-text channel elements |
usergroup | (id: string) => ReactNode | undefined | <!subteam^S…> and rich-text usergroup elements |
team | (id: string) => ReactNode | undefined | Rich-text team elements |
emoji | (name: string, unicode?: string) => ReactNode | undefined | :shortcode: tokens and rich-text emoji elements |
date | (timestamp: number, format: string) => ReactNode | undefined | <!date^…> tokens and rich-text date elements |
imageSize | (url: string) => number | undefined | Byte size printed beside an image block's title |
The renderer always supplies the mention sigil itself (@ for users and
groups, # for channels), so return just the name.
imageSize is not a mention resolver: Slack captions every image block with
the downloaded file's size ("Image title (777 B)"), which the payload does not
carry. Return the byte count for URLs you know and the renderer formats it the
way Slack does (B, kB, MB); without it the caption shows the title alone.
Fallback order
Each mention kind resolves through a fixed chain; the first defined value wins.
- Users, channels, usergroups: your resolver → the payload's fallback
label (the part after
|in<@U024BE7LH|maya>) → the raw ID. - Emoji: your resolver → the payload's explicit
unicodecode point (rich text only) → a small built-in set of common glyphs → the literal:name:text. Unknown and custom emoji names always stay readable. Supply anemojiresolver when rendering arbitrary workspace payloads; the built-in set is not a full Slack emoji catalog. - Dates: your resolver → deterministic UTC formatting of Slack's date
tokens (
{date},{date_num},{date_short},{time}, and variants) → the payload's fallback text for relative or unrecognized tokens ({ago}needs a clock, so it intentionally uses the fallback) → an ISO date.
Broadcast mentions (<!here>, <!channel>, <!everyone>) render as styled
@here/@channel/@everyone text and take no resolver.
Example
This message renders with a demo directory wired into user, channel, and
usergroup; emoji and the date token use the built-in fallbacks:
View payload JSON
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<@U024BE7LH> merged the release branch into <#C024BE91L> :tada:\ncc <!subteam^S0614TZR7>. Freeze starts <!date^1784764800^{date_short}|Jul 23, 2026>."
}
}
]
}View payload JSON
{
"blocks": [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "<@U024BE7LH> merged the release branch into <#C024BE91L> :tada:\ncc <!subteam^S0614TZR7>. Freeze starts <!date^1784764800^{date_short}|Jul 23, 2026>."
}
}
]
}The right-hand render is the same payload with no resolvers at all. Each mention falls back to its payload label or raw ID, and nothing disappears.