Slack automation with slackctl
slackctl is a single-binary CLI on $PATH. Invoke directly (e.g. slackctl user get @alice).
Installation
slackctl is a single static binary with no runtime dependencies, available for macOS, Linux, and Windows (amd64 + arm64).
If slackctl is not found on $PATH, install it for the current OS:
- macOS / Linux (recommended) — Homebrew:
brew tap cluas/tap
brew install slackctl
- Windows (recommended) — Scoop:
scoop bucket add cluas https://github.com/cluas/scoop-bucket
scoop install slackctl
- Any OS with Go installed:
go install github.com/cluas/slackctl/cmd/slackctl@latest
- Manual (any OS): download the archive for your OS/arch from the releases page, extract it, and put
slackctl(orslackctl.exeon Windows) on yourPATH.
CRITICAL: Bash command formatting rules
Claude Code's permission checker has security heuristics that force manual approval prompts. Avoid these patterns to keep commands auto-allowed.
- No
#anywhere in the command string. Use bare channel names (generalnot#general). - No
''(consecutive single quotes) or""(consecutive double quotes). Triggers "potential obfuscation" check. - Only
| jqfor filtering — no python3, no other commands.jqwith single-quote-only expressions (no"inside) is safe:- RIGHT:
slackctl search messages "query" | jq '.messages[] | .ts'
- RIGHT:
- No
||or&&chains. Run multiple slackctl commands as separate Bash tool calls. - No file redirects (
>,>>). Process JSON output directly, don't write to files.
Quick start (auth)
Auth auto-detection works on macOS, Linux, and Windows. It reads credentials
straight from local apps (no browser automation), trying Slack Desktop first,
then Chrome, then Firefox. Cookie decryption is handled per-platform: macOS
Keychain, Linux Secret Service (secret-tool), and Windows DPAPI.
Per-source platform support:
Source (auth import-*) |
macOS | Linux | Windows |
|---|---|---|---|
import-desktop (Slack Desktop) |
✅ | ✅ | ✅ |
import-chrome |
✅ | ✅ | ✅ |
import-brave |
✅ | ✅ | ✅ |
import-firefox |
✅ | ✅ | ✅ |
Notes: On Linux, Chrome/Brave/Slack cookie decryption reads the key from the
desktop keyring via secret-tool (install libsecret-tools / libsecret),
falling back to the default peanuts password when no keyring is configured.
Firefox cookies.sqlite is plaintext on every platform.
If credentials aren't available, run one of:
- Slack Desktop import (macOS/Linux/Windows):
slackctl auth import-desktop
slackctl auth test
- Firefox fallback (macOS/Linux/Windows):
slackctl auth import-firefox
slackctl auth test
- Chrome fallback (macOS/Linux/Windows):
slackctl auth import-chrome
slackctl auth test
- Or set env vars:
export SLACK_TOKEN="xoxc-..."
export SLACK_COOKIE_D="xoxd-..."
export SLACK_WORKSPACE_URL="https://myteam.slack.com"
slackctl auth test
- Or add manually:
slackctl auth add --workspace-url https://myteam.slack.com --xoxc xoxc-... --xoxd xoxd-...
slackctl auth test
Check configured workspaces:
slackctl auth whoami
Canonical workflow (given a Slack message URL)
- Fetch a single message:
slackctl message get "https://workspace.slack.com/archives/C123/p1700000000000000"
- If you need the full thread:
slackctl message list "https://workspace.slack.com/archives/C123/p1700000000000000" --thread-ts 1700000000.000000
Browse recent channel messages
slackctl message list "general" --limit 20
slackctl message list "C0123ABC" --limit 10
slackctl --attachments message list "general" --limit 20
--attachments is off by default. Pass it when the message body lives in
attachments rather than text.
Get unread messages
List all channels with unread messages:
slackctl message unread --limit 20
Also fetch the actual unread message content:
slackctl message unread --fetch --max-per-channel 5
Note: For Enterprise Grid, use --workspace with the enterprise URL for unread counts:
slackctl message unread --workspace longbridge-group --fetch
Get saved-for-later messages (Later)
List the user's saved messages (Slack's "Later" list):
slackctl message saved --limit 20
Also fetch the actual saved message content:
slackctl message saved --fetch
Filter by state (saved = in progress, completed, archived; default is all):
slackctl message saved --filter saved --fetch
For Enterprise Grid, the enterprise workspace is used automatically when needed.
Send, edit, delete, or react
slackctl message send "general" "here is the report"
slackctl message send U01AAAA "hello via DM"
slackctl message send "general" "threaded reply" --thread-ts 1700000000.000000
slackctl message edit "general" 1700000000.000000 "updated text"
slackctl message delete "general" 1700000000.000000
slackctl message react add "general" 1700000000.000000 "eyes"
slackctl message react remove "general" 1700000000.000000 "eyes"
Send to a user ID opens a DM automatically.
List channels + create/invite
slackctl channel list
slackctl channel list --limit 50
slackctl channel new "incident-war-room"
slackctl channel new "incident-leads" --private
slackctl channel invite "incident-war-room" "U01AAAA,U02BBBB"
Files (upload, download, list, info, delete)
Upload a file to a channel or thread:
slackctl file upload ./report.pdf "general" --title "Weekly Report" --message "Here is this week's report"
slackctl file upload ./screenshot.png "general" --thread-ts 1700000000.000000
slackctl file upload ./photo.jpg U01AAAA --message "Check this out"
Upload from stdin (pipe):
cat output.log | slackctl file upload - "general" --filename "build.log" --title "Build Log"
Download a file:
slackctl file download F0123ABCDEF --output ./downloaded.png
slackctl file download F0123ABCDEF --url-only
List, inspect, delete files:
slackctl file list --channel "general" --limit 10
slackctl file list --types images --limit 20
slackctl file info F0123ABCDEF
slackctl file delete F0123ABCDEF
Search (messages + files)
slackctl search messages "deploy failed" --limit 10
slackctl search files "report" --limit 5
Mark as read
slackctl channel mark "general" 1700000000.000000
Canvas
Fetch a Slack canvas and convert to Markdown:
slackctl canvas get "https://workspace.slack.com/docs/T123/F456"
slackctl canvas get F0123ABCDEF
Users
slackctl user list --limit 100
slackctl user get "@alice"
slackctl user get U01AAAA
Multi-workspace
If you have multiple workspaces, pass --workspace to disambiguate:
slackctl message list "general" --workspace "myteam" --limit 10
slackctl auth set-default https://myteam.slack.com
All output is JSON
All commands output JSON to stdout. Use | jq for filtering:
slackctl user get U01AAAA | jq '.email'
slackctl message list "general" --limit 5 | jq '.[].text'
References
- references/commands.md: full command map + all flags
- references/targets.md: URL vs channel targeting rules
- references/output.md: JSON output shapes