Slack
Your Slack messages and mentions, in the panel and on the bar
palupdated 2026-09-29IntegrationIn pal's registryMenu bar: Unreads
In pal's registry: the button shows it in pal, which asks before installing; the command does the same from a terminal. This page can't see what your pal has installed.




What it does
What Slack is holding for you: direct messages, mentions and thread replies with who sent them, your channels, search, and your status and Do Not Disturb. Signs in through the Slack desktop app you already use, or a token.
- Direct messages, mentions and thread replies with avatars and the message; unread channels under them
enteropens the message in Slack; reply or mark as read without leaving the panel- A bar item that counts only what is addressed to you, red while a direct message waits
- Every channel and conversation you are in, with the topic and member count
- Search as you type with Slack's own filters:
from:@name,in:#channel,has:link - Set your status, presence and Do Not Disturb from the panel
Palettes 4
-
Unreads
unreadsliveDirect messages, mentions and threads addressed to you, then the channels that are only unread; each row with who said what and how long ago.
- ↵Open the conversation in Slack
- ⌘↵Reply with the message typed in the bar
- ⌘⇧RMark as read
- ⌘⇧OOpen in the browser
- ⌘CCopy the link
-
Channels
channelslistcatalogrefreshes every 1hEvery channel, group and direct message you are in, with the topic and the member count; a message typed in the bar is sent with cmd+shift+r.
- ↵Open in Slack
- ⌘⇧RSend the message typed in the bar
- ⌘⇧OOpen in the browser
- ⌘CCopy the link
-
Search Slack
searchinputSlack's message search as you type, its syntax passed through: from:@name, in:#channel, has:link, before:yesterday.
- ↵Open the message in Slack
- ⌘⇧OOpen in the browser
- ⌘CCopy the text
-
Status
statusliveYour status, presence and Do Not Disturb as the first rows; set a status from the presets or type one (text, emoji, expiry in the search bar), clear it, snooze notifications for 30 minutes, an hour, until tomorrow or for the minutes you type, go away or active.
- ↵Set it; Set a status… and Do Not Disturb for… take their values from the bar's fields
Actions
- OpenThe conversation in the Slack app, at the message when one is known.↵
- ReplyThe message typed in the bar (the row's argument) posts to the conversation (into the thread for a threaded mention); a one-field form when picked without it.⌘↵
- Send a messageIn Channels: the message typed in the bar posts to the conversation; a form when picked without it.⌘⇧R
- Mark as readUp to the latest message; the count falls at the next refresh.⌘⇧R
- Open in browserThe web client's archive page.⌘⇧O
- Copy link⌘C
- Copy textA found message's text.⌘C
- Set statusA preset, with its expiry; on Set a status…, the text, emoji and expiry typed in the bar.
- Clear status
- Do Not Disturb30 minutes, 1 hour, until tomorrow, or the minutes typed in the bar; End Do Not Disturb while it is on.
- Set away / Set active
Settings
Extension, [extensions.slack]
authapp reads the desktop app's own session (its token from Local Storage and its cookie from the cookie jar, decrypted with the Safe Storage password in the keychain), so it sees exactly your inbox; token uses a user token from a Slack app of your own. One of app, token.
tokenA user token (xoxp-...) with channels:read, groups:read, im:read, mpim:read, channels:history, groups:history, im:history, mpim:history, users:read, chat:write, search:read, users.profile:write, users:write, dnd:write. Only with Sign in set to token.
workspaceThe workspace to use when the app is signed in to several, by id (T...) or domain (acme for acme.slack.com); empty lists every signed-in one, with status and search on the first.
statusesOne per line as :emoji: text (expiry); the expiry is 30m, 2h, 1d or today, or left out for no expiry.
presenceOne users.getPresence per person among the unread direct messages, remembered a minute; off makes no such call, for a workspace where many direct messages are open at once.
Defaults shown. Change them in pal's settings window or in the config file.
The README
Everything Slack does, key by key, from the extension's own README.md
Slack
What Slack is holding for you, the part that is yours: direct messages, @-mentions and replies in threads you follow as rows with who said what and how long ago, the channels that are merely unread named under them; the channels you are in; Slack's own search typed into the panel; your status, presence and Do Not Disturb. One bar item counts what is addressed to you and stays hidden otherwise.
| palette | id | kind | what Enter does |
|---|---|---|---|
| Unreads | slack-unreads |
live | opens the conversation in the Slack app, at the message |
| Channels | slack-channels |
indexed, 1 h, catalog | opens the conversation in the Slack app |
| Search Slack | slack-search |
input | opens the message in the Slack app |
| Status | slack-status |
live | sets the status, snoozes, or flips presence |
Unreads is built on addressed versus merely unread. A workspace of
thirty channels is never at zero unread, so a list or a count of those
would be permanently lit and say nothing. A direct message, an @-mention
(@you, @here, @channel) or a reply in a thread you follow is
someone asking you for something: those are the rows and the count.
Channels that are only unread come last, named, without a count, their
topic or purpose as the subtitle (else "New messages"). The
sections are Direct messages, Mentions, Threads, Channels, newest first
in each. A row is the conversation (the person, #channel, the people of
a group message) with the sender's avatar, the message (in a channel, the
one that names you, not whatever was said last), a count badge (red for
a direct message or mention, blue for thread replies, 1+ when the run
is longer than a page), a presence dot for a direct message (green while
the person is active, grey while away; in a group message, whoever wrote
the message shown) and how long ago. The detail pane (⌘I, or rest
on the row) is the unread run itself, oldest first, up to eight
messages. Actions: Open in Slack (Enter), Reply (⌘Enter; the message
is the row's typed argument: Tab into the Message field in the bar, and
⌘Enter posts it to the conversation, or into the thread for a threaded
mention; a pick without it, from pal run or a hotkey, is a one-field
form), Mark as read (⌘⇧R, up to the latest message), Open
in browser (⌘⇧O, the archive page), Copy link (⌘C). Thread rows have
no message text (Slack's counts name the channel, not the thread) and no
Reply or Mark as read.
Channels is every channel, private channel, group message and direct
message you are in, channels first, then by name, with the topic or
purpose, the member count and a tag for private, group and DM. Enter
opens it; Send a message (⌘⇧R) posts the message typed in the bar's
field (the same chat.postMessage a reply uses; a form when picked
without it). Listed once an hour (⌘R for now). Search Slack sends what you type to
search.messages as is, so Slack's own syntax works: free text,
from:@name, in:#channel, has:link, before:yesterday, on: a
date; rows are the message, who said it where, and when (the hits whose
names are known show first, in order, while a sender the directory lacks
is looked up). Status lists
what is set now first (your status with its expiry, Do Not Disturb, your
presence), then the presets from the statuses setting and a "Set a
status…" row whose text, emoji and expiry are typed in the search bar,
Do Not Disturb for 30 minutes, an hour, until tomorrow or for the minutes
you type ("Do Not Disturb for…"), and Set away / Set active. A pick of
either typed row that arrives without its values (a hotkey, pal run)
asks for them in a form with the same fields.
Signing in
auth = "app" (the default) reads the Slack desktop app's own
session, so what the palette sees is exactly the inbox on your screen:
no Slack app to create, nothing for an admin to approve, and the client
API's client.counts, the one call that answers every unread at once
(the public API has no unread endpoint). What is read, all of it
read-only, both files copied before reading since the app holds them
open:
~/Library/Application Support/Slack/Local Storage/leveldb/(Linux:~/.config/Slack/, the snap or the flatpak path): the key ending inlocalConfig_v2, the app's record of its signed-in workspaces (id, name, domain, your user id, thexoxc-token). It is a LevelDB, so the SSTables and the write-ahead log are parsed (leveldb.ts); a value written since the last compaction is only in the log.~/Library/Application Support/Slack/Cookies: thedcookie for.slack.com, AES-128-CBC under a key derived from the app's "Slack Safe Storage" password in the login keychain (security find-generic-password; the one step that can prompt, and "Always Allow" ends that). On Linux the password comes from the Secret Service (secret-tool lookup application Slack) for av11value, or is Chromium's fixed "peanuts" for av10one.
Both are kept in memory only, never written anywhere, and extracted
again when Slack answers invalid_auth (you signed in again), once per
call. Nothing is sent anywhere but <workspace>.slack.com.
auth = "token" uses a user token (xoxp-...) from a Slack app of
your own, as a bearer. It has no client.counts: unreads then come from
conversations.info per conversation you are in (unread_count_display),
which gives direct messages and unread channels but no mention counts and
no thread replies (a mention in a channel is still shown when its unread
run is fetched). Scopes: channels:read, groups:read, im:read,
mpim:read, the four *:history, users:read, chat:write,
search:read, users.profile:write, users:write, dnd:write.
Without either, each palette is one hint row saying what is missing; Enter on it opens the extension's settings.
Requests
One client.counts per refresh gives every conversation's last_read,
latest, mention count and unread flag and the thread counts. A
conversations.history is spent only on an addressed conversation
(never on the quiet channels), only for the newest twelve, and only once
per change of its latest, so a refresh where nothing moved is the one
call. The inbox is shared between the bar item and the palette for 30 s,
so the panel showing and the bar refreshing on it cost one fetch. Names
and avatars come from users.list and users.conversations, kept an
hour, with users.info / conversations.info for an id they lack.
The presence dots are one users.getPresence per person among the
direct messages listed (the inbox carries no presence), eight in flight
at once, each remembered a minute so a re-list inside it asks nothing;
a lookup that fails or takes over 2 s leaves its row without the dot and
never holds the listing longer. presence = false makes none of them.
Every request has a 10 s timeout; a 429 is remembered for its
Retry-After and every call until then fails at once (a hint row, a
stale bar item) instead of piling onto the limit.
Keyboard
| keys | action | where |
|---|---|---|
enter |
Open in Slack (the app, at the message when one is known) | Unreads, Channels, Search |
cmd+enter |
Reply: the message typed in the bar's field posts to the conversation, into the thread for a threaded mention (a form when picked without it) | Unreads, not a thread row |
cmd+shift+r |
Mark as read, up to the latest message | Unreads, not a thread row |
cmd+shift+r |
Send a message: the message typed in the bar's field, posted to the conversation (a form when picked without it) | Channels |
cmd+shift+o |
Open in browser (the web client's archive page); marked rows each in a tab | Unreads, Channels, Search |
cmd+c |
Copy link (Copy text on a search hit); marked rows one per line | Unreads, Channels, Search |
enter |
Set status, Clear status, Pause notifications, End Do Not Disturb, Set away / active; on "Set a status…" the bar takes the status text, the emoji (:speech_balloon: unless you say) and the expiry (none, 30m, 1h, 2h, 4h, tomorrow), on "Do Not Disturb for…" the minutes |
Status |
cmd+i |
The detail pane: the unread run | Unreads |
Settings
[extensions.slack]:
| key | type | default | what |
|---|---|---|---|
auth |
app / token |
app |
The desktop app's session, or a user token. |
token |
secret | (none) | The user token, with auth = "token"; the file holds a keychain: reference. |
workspace |
text | (none) | The workspace when the app is signed in to several, by id (T...) or domain; empty lists every one (status and search on the first). |
statuses |
list | five presets | One per line as :emoji: text (expiry); the expiry is 30m, 2h, 1d or today, or left out. Common Slack shortcodes are drawn as the emoji. |
presence |
boolean | true |
A presence dot on each direct message row (green active, grey away), one users.getPresence per person, remembered a minute; off makes no such call. |
unreads item settings, [bar.items."slack/unreads".settings]:
| key | type | default | what |
|---|---|---|---|
refresh |
number (s) | 120 |
Seconds between refreshes of the item (10 at least). |
When the bar item is drawn and whether a direct message makes it urgent are its rules (quiet, dm) under Settings > Bar: narrow quiet to slack.attention == 0 and slack.channels == 0 to keep the glyph while a channel is merely unread, turn dm's Urgent off for a plain count. Keeping it at all times is the core's show = "always" under [bar.items."slack/unreads"] (docs/config.md).
The bar item
Unreads (slack/unreads): the count of what is addressed to you
(direct messages + mentions + thread replies) as the badge, hidden at
zero, urgent while a direct message waits. Refreshed every refresh
seconds (its item setting) and when the panel shows, after a wake and when the network is
back. The popover is a view of the item's own: a section per kind with
every row (the popover scrolls), each the sender's picture (an initial in a colour
while it is not fetched), the conversation, the message on one line,
the time and a count (red for a direct message or a mention, blue for
thread replies), a direct message's presence as a dot on the picture;
the channels that are only unread as a row of badges
under them (a click opens one), then the keys. A cursor marks the row
the keys act on: the arrows (or j/k) move it, a click on a row sets
it.
| key | does |
|---|---|
| Enter | opens the row in the Slack app, at the message |
r |
reply: the search row becomes a text field, Enter posts it to the conversation (into the thread for a threaded mention), Escape cancels |
m |
marks the row read, up to its latest message |
a, ⌘⇧A |
marks every listed conversation read |
o |
opens Slack |
p |
opens the Unreads palette in the popover |
⌘⇧O, ⌘C |
the row in the browser, its link copied |
A refresh that fails leaves the item stale (muted) until the next one succeeds; not signed in hides it.
What it does not do
- Read a thread's replies: Slack's counts say which channel has unread replies in threads you follow, not which thread; the row names the channel and the count, Enter opens the channel in Slack.
- Mentions and thread replies with a user token: only the desktop app's
session has
client.counts. - Reactions, files, huddles: a message that is only a file shows as
📎 name; a huddle as "started a huddle". - Several workspaces for status and search: those act on the
workspacesetting's, else the first signed-in one; Unreads and Channels list every one.
Platforms
macOS and Linux. The Linux cookie path (Secret Service or "peanuts") is written to Chromium's documented shape and is not verified on a box running Slack; the LevelDB and the rest are the same on both.






