AI-Assisted Block Kit Design for Faster Slack UX
Use Claude or ChatGPT to draft and iterate Block Kit JSON for ops messages, run a tight validation loop, dodge common AI mistakes, and review before shipping.
- #slack
- #chatops
- #block-kit
- #ai
The first time I hand-wrote a Block Kit payload for a deploy-status message, I lost an entire afternoon. Not to anything interesting — to a missing comma, a mrkdwn field that should have been plain_text, and a section block that quietly refused to render because I’d nested an actions element where it didn’t belong. Slack’s API didn’t throw a helpful error. It just rendered a sad, half-broken card in the channel, and I had to bisect the JSON by hand. So when I started letting an LLM draft these layouts for me, it felt like cheating. It mostly is. But it’s the kind of cheating that still requires you to know the rules, because the model will confidently hand you JSON that looks perfect and doesn’t work.
This post is about using AI — Claude, ChatGPT, whichever you keep open — to generate and iterate Block Kit JSON for operational Slack messages, and about the validation loop that keeps the model honest. The short version: treat the AI like a fast junior engineer who’s read the docs once. Quick, eager, occasionally wrong in ways that need a human’s eyes before anything reaches a real workspace.
Why Block Kit is a good fit for AI
Block Kit is structured, declarative, and well-documented, which is exactly the kind of surface LLMs do well on. You describe the message — “a status card with a header, two side-by-side fields for service and environment, a divider, and an approve/reject button row” — and the model emits a blocks array. There’s no imperative logic to get subtly wrong, no race conditions, no state. It’s data.
That’s also why it’s a good place to start delegating. If you’re building chatops tooling and you’ve been copy-pasting block snippets from old messages, describing what you want in plain English and getting a first draft back is a real speedup. The catch is that “first draft” is load-bearing.
A prompt that produces usable blocks
The quality of what you get back scales directly with how specific you are. Vague prompts produce vague, generic cards. Here’s the shape of a prompt that works:
Generate a Slack Block Kit
blocksarray (JSON only, no surrounding code) for an incident-notification message. Include: aheaderblock with the incident title; asectionwith twofields(mrkdwn) for Severity and Service; acontextblock showing the detection timestamp; adivider; and anactionsblock with two buttons, “Acknowledge” (primary) and “Escalate” (danger). Use valid block types only and keep the array under 50 blocks.
What you get back will usually look like this:
{
"blocks": [
{
"type": "header",
"text": { "type": "plain_text", "text": "🔥 API latency spike — checkout-svc" }
},
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*Severity:*\nSEV-2" },
{ "type": "mrkdwn", "text": "*Service:*\ncheckout-svc" }
]
},
{
"type": "context",
"elements": [
{ "type": "mrkdwn", "text": "Detected 2026-06-16 14:02 UTC by Prometheus" }
]
},
{ "type": "divider" },
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "Acknowledge" },
"style": "primary",
"action_id": "ack_incident"
},
{
"type": "button",
"text": { "type": "plain_text", "text": "Escalate" },
"style": "danger",
"action_id": "escalate_incident"
}
]
}
]
}
That’s a solid draft. But notice what’s already lurking: the model invented action_id values, which is good, but it could just as easily have omitted them — and a button with no action_id will fire an interaction event your handler can’t route. This is the kind of thing a human has to catch.
The validation loop
Never paste model output straight into a chat.postMessage call against a live channel. Run it through a loop that closes the gap between “looks right” and “is right”:
- Schema-check the JSON. Is it parseable? Are all block types real (
section,header,divider,context,actions,input,image,video,rich_text)? - Paste it into Slack’s Block Kit Builder. This is the single highest-leverage step. The Builder renders the payload live and flags invalid fields immediately.
- Post it to a private test channel, never the real one, with your own bot token.
- Feed errors back to the model. When the Builder complains, paste the exact error back to the AI and ask it to fix that specific issue.
Here’s a minimal Bolt (JavaScript) snippet for the test-channel step:
const { App } = require('@slack/bolt');
const app = new App({
token: process.env.SLACK_BOT_TOKEN,
signingSecret: process.env.SLACK_SIGNING_SECRET,
});
async function postDraft(blocks) {
await app.client.chat.postMessage({
channel: process.env.TEST_CHANNEL_ID, // a private sandbox, not #incidents
text: 'Incident notification (fallback text for notifications)',
blocks,
});
}
Note the text field alongside blocks. The model frequently omits it, and Slack uses it as the fallback for notifications and screen readers. A card with no fallback text is an accessibility and notification bug that renders perfectly in the Builder — exactly the class of error that slips past a quick visual check.
Pro Tip: Keep a validate.js script that parses the JSON, asserts every block has a known type, and counts the array length before you ever hit the Slack API. It turns a category of silent failures into a loud one.
The mistakes AI makes, and how to spot them
After generating a few hundred of these, the failure modes are predictable. The model is pattern-matching on training data that includes outdated Slack docs and other people’s broken Stack Overflow answers, so:
- Invalid or hallucinated block types. I’ve seen
"type": "card","type": "columns", and"type": "alert"— none of which exist. Block Kit has a fixed, smallish set of block types. If you don’t recognize it, it’s wrong. - Deprecated fields. Older
attachments-style color bars get mixed into modernblocksarrays.mrkdwnvsplain_textconfusion is constant —headerblocks only acceptplain_text, and the model will sometimes hand youmrkdwnthere, which fails validation. - The 50-block limit. A message can have at most 50 blocks; a modal view, 100. Ask for a rich dashboard-style message and the model will happily generate 60 blocks. It does not track the limit unless you remind it.
- Missing
action_idon interactive elements. As above — interactive without anaction_idis a routing dead end. - Text length overflows.
sectiontext caps at 3000 characters, button text at 75. The model doesn’t measure.
None of these are exotic. They’re exactly the bugs a competent junior would make on their first week, which is the right mental model. The AI is fast and gets you 85% of the way; the human supplies the 15% that’s specific to your workspace, your handlers, and Slack’s actual constraints. If you’re iterating on prompts for this repeatedly, a structured workspace like the prompt workspace or a saved set of prompt templates saves you re-typing the same constraints every time.
Keep the model away from your secrets
One discipline that matters more than it seems: never hand the model real tokens, signing secrets, or channel IDs from production. When you ask an AI to generate the Bolt wiring around your blocks, it’ll cheerfully scaffold a config with placeholders — but it’s tempting to paste your real xoxb- token in to “make the example concrete.” Don’t. Keep secrets in environment variables, pass the model redacted examples, and let it work on the structure, not the credentials. The blocks themselves are just data; the auth around them is where the blast radius lives.
This is the same reason you verify webhook signatures on the inbound side (more on that below) — the boundary between “AI drafted this” and “this runs against my workspace” is where a human signs off.
Iterating toward production
Once a draft survives the Builder and a test channel, the iteration is conversational. “Make the severity field a colored context block.” “Add an overflow menu with snooze options.” “The button row is too wide on mobile — split it.” The model is genuinely good at these incremental edits because it has the working payload in context and only has to mutate it.
Two habits keep this productive. First, always re-validate after every edit — a one-line change can introduce a 3001-character section. Second, version your known-good payloads. Treat the validated JSON as a fixture you can diff against, the same way you’d treat any other reviewed artifact. Tools like Cursor or GitHub Copilot inside your editor make this loop tight, since you can keep the fixture, the validator, and the Bolt handler in the same view.
Wrapping Up
AI-assisted Block Kit design is one of the cleaner wins in chatops automation: a structured target, fast drafts, and a validation loop that’s easy to make rigorous. The model behaves like a quick junior engineer — it gets you a working skeleton in seconds and makes the same handful of mistakes every time, so you learn to scan for them. Run every payload through the Block Kit Builder and a sandbox channel, feed errors back, keep your real secrets out of the prompt, and have a human approve before anything reaches a live workspace. Do that, and you’ll spend your afternoons on the interesting parts of ops UX instead of bisecting JSON by hand.
Download the Free 500-Prompt DevOps AI Toolkit
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.