blockkit-forge
Skill3 starsChanged 19 days agoApache-2.0View on GitHub
Use when building a Slack Block Kit message from real data that a bot will post, update or thread, such as a notification card, a ticket or case log, a digest, or a status that changes. Covers the live enums (read from blocks.validate, which knows more block types than the docs), a dry-post as the bot (a message scheduled a week out, deleted, the queue read back), Builder preview, text from another system made safe (escape order, cut before escape, verbatim, HTML to mrkdwn, URLs), scannable cards, collapsed long text, status pills, people tagged without a ping, live updates and thread timelines, fallbacks and read-back. Triggers on Block Kit, blocks, chat.postMessage, chat.update, mrkdwn, invalid_blocks, Block Kit Builder, a Slack card, a notification to Slack. Not for learning Block Kit from zero (Slack's own block-kit skill does that), modals or Home tabs, or a message written in a person's own voice.
In this repository9 other files it gives its agents.
- comb-pdf-form-fillingSkill
- din5008-letterSkill
- drive-auditSkill
- excalidrawSkill
- human-reviewSkill
- qr-codeSkill
- screenshot-calloutsSkill
- screenshot-overlaysSkill
- video-to-readingsSkill
The file
We split this skill into the rules it gives an agent
1 rule. About 0 are common, 0 are distinctive, and 1 is very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
1
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 72
Post, then prove it: Read the message back from the channel.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
blockkit-forge: Block Kit from real data, proven before anyone sees it
A Block Kit message fails in three quiet ways. Slack refuses it (invalid_blocks) and the job dies at
3 a.m. It renders, but text from another system pings the channel or breaks a link. Or it is correct
and unreadable, because twenty of them stack into a wall. This skill handles all three, and it never
trusts memory for the schema: Slack ships new blocks every few weeks.
It complements Slack's official block-kit skill in
slackapi/slack-skills-plugin (MIT), which teaches
surfaces and blocks from the live docs and also calls blocks.validate. Learn Block Kit there. This
skill starts where that one ends: a bot posting data it did not write, again and again, into one
channel.
Image: An invented notification card on the left: the header "Northwind Ltd: Migrate the nightly reporting jobs", a grey line with the ticket number and an italic created time, a purple "In Process" status pill, a two-by-three grid with Requester, Manager (cc) and Owner as blue tags and Priority, Category and Due as text, a collapsed full-width "Request" box with a one-line preview, and an "Open in the record" button. On the right, its thread: a status note with "New" and "In Process" as code chips joined by an arrow, a comment, and a reply, each with tagged names and an italic time
The picture is in the repository's docs/assets/, so this folder stays text-only. Every name and
number in it is invented. scripts/make_illustration.py draws it, and
examples/card.json is the same card as real blocks.
What you need
- For
schemaandvalidate: nothing but HTTPS to slack.com. No account, no token. - For
dry-postandcleanup: a Slack app with the bot scopechat:write, installed to the workspace, and invited to the channel (/invite @yourbot). Its bot token (xoxb-) goes on stdin from a file or a secret manager. The channel is its id (C..., from the channel's About panel), not its name. - For a production bot built on these patterns:
chat:write, pluschannels:historyorgroups:history(private channel) to read its own posts back, plususers:readandusers:read.emailif it turns names or emails into<@U...>tags.
The method, in order
- Read the live enums, not the docs.
scripts/blockkit.py schemaasksblocks.validatewith a deliberately wrong value for each probed field, and Slack answers with the allowed values. On 2026-10-22 its block-type enum named 34 types, about 14 of them on no official page. Three of those (callout,condition,contact_card) were also accepted in a bot message. Undocumented blocks are for exploration, not for a production bot: if you use one anyway, keep a documented fallback and look at it on desktop and phone. references/catalogue.md has both lists. - Design for scanning, because nobody reads a channel: one card per item, the state as a coloured pill, a field grid with even rows, long text folded, one button, a divider. references/patterns.md.
- Treat every string from another system as hostile. Decode, cut, then escape
&first, then<and>. Setverbatim: trueon everymrkdwntext object that carries it, never sendlink_names, and preferplain_textandrich_textwhere no formatting is needed. Escape the top-leveltexttoo. references/untrusted-text.md. - Validate the shape:
python3 -I scripts/blockkit.py validate payload.json. No token. It checks shape only: not the surface, and not the per-message totals (twodata_visualizationblocks, 12,000markdowncharacters). - Dry-post as the bot:
op read op://vault/slack-bot/token | python3 -I scripts/blockkit.py dry-post C0123ABCD payload.json(any secret manager, or< token-file; neverechothe token, which writes it to shell history). Each block alone, then the whole message, scheduled a week out and deleted at once, then the queue read back. It is a live write with a safety margin, not a sandbox: every id is recorded, a crash still deletes and reads back, and any doubt exits non-zero and names the ids (blockkit.py cleanup CHANNELremoves them). It tests the blocks only. The top-leveltext, unfurl settings and metadata are yours to check. - Look at it. Block Kit Builder renders desktop and mobile. A payload in its URL hash did not load
on 2026-10-22 (it reset to
{}); paste the JSON into the editor instead. Then post, and look at the real channel at desktop and phone width. - Post, then prove it. Read the message back from the channel. Keep a minimal fallback card for
invalid_blocks. Record thetsbefore the next post, so a crash cannot post twice.
Rules worth knowing (2026-10-22; [docs] where Slack states it, [measured] where we saw it)
- [measured]
blocks.validatereturns allowed enums ahead of the docs. Errors carry a JSON pointer. - [docs]
header.leveltakes 1 to 4. [measured] Its error text says "must be less than 4" anyway. - [docs]
containerwithis_collapsibleanddefault_collapsedfolds, andwidth: fullspans the message. [measured] Itssubtitlestayed visible while it was folded, on desktop. - [docs] A
<@U...>in an app's message notifies the user only if they are already a channel member. - [docs]
mrkdwntext objects default toverbatim: false, which turns some bare names into mentions. - [measured] A tag ADDED BY AN EDIT (
chat.update) did not notify a channel member in one live test, so "post plain, then edit in the tag" can show real tags without a ping. Undocumented: test it on yourself once per workspace before you rely on it. - [measured]
chat.postMessagewiththread_tscan land outside the thread if the parent is gone. Compare the response'smessage.thread_tswith the parent, and delete a misplaced reply. - [docs] A scheduled message due within 60 seconds cannot be deleted. [measured] One deleted minutes before its time was still delivered, so schedule test messages a week out.
- [docs] Slack schedules at most 30 messages per 5 minutes per channel;
dry-postspaces them. - [measured] Never post with a
usernameoricon_*override if the bot must edit or delete that message later. The bot's ownchat.deleteansweredcant_delete_message. For a different name or picture, use a dedicated app. references/patterns.md, "Posting identity".
Scripts
scripts/blockkit.py schema [--type T],validate FILE,dry-post CHANNEL FILE,cleanup CHANNEL,selftest(token on stdin). Retries a rate limit, refuses redirects, and exits non-zero on any failure.escape_mrkdwn,cut_then_escapeandhtml_to_mrkdwnare importable reference implementations of step 3.scripts/selftest.py: offline checks of the enum parser, loading, escaping, argument handling, the retry, the token guard and every dry-post failure path. No network.scripts/make_illustration.py: draws the example picture (Pillow).
Related
- Slack's
block-kitskill for Block Kit itself.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

