Slack Error Guide: 'invalid_blocks' — Fix Malformed Block Kit Payloads
Fix the Slack invalid_blocks error: validate Block Kit structure, catch bad block/element types, over-length text, too many blocks, and missing fields, with real curl and response_metadata examples.
- #slack
- #api
- #troubleshooting
- #errors
Stuck on this Slack error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
The invalid_blocks error means Slack could not parse or accept the blocks array you sent to a method like chat.postMessage, chat.update, views.open, or views.publish. The payload was syntactically valid JSON, but the Block Kit structure inside it violated a rule: an unknown block or element type, a field that is too long, too many blocks, a missing required property, or a value that is not allowed in that position. Unlike a rate limit, this is a payload problem — retrying the identical body will fail every time until you fix the blocks.
The response returns HTTP 200 with ok: false, and the useful detail is in response_metadata.messages:
{
"ok": false,
"error": "invalid_blocks",
"response_metadata": {
"messages": [
"invalid block: text field cannot exceed 3000 characters [json-pointer:/blocks/2/text/text]"
]
}
}
The json-pointer in the message tells you exactly which block and field failed — always read it before touching anything else.
Symptoms
- Method call returns
200with"ok": falseand"error": "invalid_blocks". response_metadata.messagescontains one or more human-readable reasons withjson-pointerlocations.- Messages that render in Block Kit Builder still fail from your code (usually a length or count difference from real data).
- A message that worked with test data fails once real content (long logs, many rows) is substituted.
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"channel":"C0123456789","blocks":[{"type":"section"}]}'
{
"ok": false,
"error": "invalid_blocks",
"response_metadata": { "messages": ["invalid block: missing required field [json-pointer:/blocks/0]"] }
}
Common Root Causes
1. Section block with no text and no fields
A section must contain either text or fields. An empty section is rejected.
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","blocks":[{"type":"section"}]}'
{"ok":false,"error":"invalid_blocks","response_metadata":{"messages":["invalid block: missing required field [json-pointer:/blocks/0]"]}}
2. Text field exceeds the length limit
A section text is capped at 3000 characters; a plain-text button text at 75. Dumping a raw log line into a section blows past 3000.
BIG=$(head -c 4000 < /dev/zero | tr '\0' 'x')
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d "{\"channel\":\"C0123456789\",\"blocks\":[{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\"$BIG\"}}]}"
{"ok":false,"error":"invalid_blocks","response_metadata":{"messages":["invalid block: text field cannot exceed 3000 characters [json-pointer:/blocks/0/text/text]"]}}
3. Too many blocks
A message allows at most 50 blocks; a modal/home view allows 100. Rendering one block per table row overflows quickly.
BLOCKS=$(python3 -c 'import json;print(json.dumps([{"type":"divider"} for _ in range(60)]))')
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d "{\"channel\":\"C0123456789\",\"blocks\":$BLOCKS}"
{"ok":false,"error":"invalid_blocks","response_metadata":{"messages":["too many blocks (max 50) [json-pointer:/blocks]"]}}
4. Unknown or misspelled block/element type
A typo in a type ("secton", "buttton") or using a modal-only element in a message yields invalid_blocks.
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","blocks":[{"type":"secton","text":{"type":"mrkdwn","text":"hi"}}]}'
{"ok":false,"error":"invalid_blocks","response_metadata":{"messages":["unknown block type \"secton\" [json-pointer:/blocks/0/type]"]}}
5. Text object type mismatch
Some fields require plain_text (button labels, input labels, option text) and reject mrkdwn. Sending the wrong type fails validation.
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","blocks":[{"type":"actions","elements":[{"type":"button","text":{"type":"mrkdwn","text":"*Go*"}}]}]}'
{"ok":false,"error":"invalid_blocks","response_metadata":{"messages":["invalid block: button text must be plain_text [json-pointer:/blocks/0/elements/0/text]"]}}
6. Input block used outside a modal
input blocks are only valid in views (modals/home tab), not in messages. Putting one in chat.postMessage fails.
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","blocks":[{"type":"input","label":{"type":"plain_text","text":"Name"},"element":{"type":"plain_text_input","action_id":"n"}}]}'
{"ok":false,"error":"invalid_blocks","response_metadata":{"messages":["input blocks are not allowed in messages [json-pointer:/blocks/0]"]}}
Diagnostic Workflow
Step 1: Read response_metadata.messages and the json-pointer
The pointer is the fastest path to the offending field. Print just the messages array:
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d @payload.json | python3 -c 'import sys,json;print("\n".join(json.load(sys.stdin).get("response_metadata",{}).get("messages",[])))'
invalid block: text field cannot exceed 3000 characters [json-pointer:/blocks/2/text/text]
/blocks/2/text/text means the third block’s text is too long.
Step 2: Confirm the JSON is well-formed first
invalid_blocks is a semantic error, not a JSON parse error — but validate the file locally so you are not chasing a stray comma:
python3 -m json.tool payload.json > /dev/null && echo "valid JSON"
Step 3: Isolate the failing block
Post the blocks one at a time (or bisect the array) to find which index Slack rejects when the pointer is ambiguous:
python3 -c 'import json;b=json.load(open("payload.json"))["blocks"];print(json.dumps(b[2:3]))' > one.json
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d "{\"channel\":\"C0123456789\",\"blocks\":$(cat one.json)}"
Step 4: Check counts and lengths against the limits
python3 - <<'PY'
import json
b=json.load(open("payload.json"))["blocks"]
print("block count:", len(b), "(max 50 msg / 100 view)")
for i,blk in enumerate(b):
t=blk.get("text",{}).get("text","")
if len(t)>3000: print(f"block {i} section text {len(t)} > 3000")
PY
Step 5: Validate the fixed payload with views.open (dry run for modals)
For modal blocks, views.open surfaces the same invalid_blocks detail without posting to a channel:
curl -s -X POST https://slack.com/api/views.open \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"trigger_id":"REDACTED_TRIGGER_ID","view":{"type":"modal","title":{"type":"plain_text","text":"Test"},"blocks":[]}}'
Example Root Cause Analysis
A deploy bot posts a summary block per changed service. On a big release it sends 63 section blocks and starts failing:
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d @deploy_summary.json | python3 -c 'import sys,json;d=json.load(sys.stdin);print(d["error"], d.get("response_metadata",{}).get("messages"))'
invalid_blocks ['too many blocks (max 50) [json-pointer:/blocks]']
Test releases only touched a handful of services, so the 50-block ceiling was never hit locally. The fix is to cap the message and move the long list into a thread or a file: render a summary header plus the top N services in the main message, then attach the full list as a snippet or post the remainder in the thread.
python3 -c 'import json;b=json.load(open("deploy_summary.json"))["blocks"];print("kept", min(len(b),48))'
Capping at 48 blocks (leaving headroom under 50) and threading the overflow makes the post succeed while keeping the channel readable.
Prevention Best Practices
- Always read
response_metadata.messagesand thejson-pointer; it names the exact block and field, so never debuginvalid_blocksblind. - Enforce Block Kit limits in code before sending: ≤50 blocks per message (≤100 per view), 3000-char section text, 75-char button/label text.
- Test with realistic, worst-case data (long log lines, many rows), not small fixtures — most
invalid_blocksfailures only appear at real volume. - Use
plain_textwhere required (buttons, input labels, option text) andmrkdwnonly where allowed. - Keep
inputblocks in modals/home views; usesection+ accessory oractionsin messages. - Prototype in Block Kit Builder, then diff its JSON against what your code emits with real content.
- For ad-hoc triage of a failing payload, the free incident assistant can point at the offending field. See more in Slack guides.
Quick Command Reference
# Print only the validation messages
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d @payload.json | python3 -c 'import sys,json;print("\n".join(json.load(sys.stdin).get("response_metadata",{}).get("messages",[])))'
# Validate JSON locally
python3 -m json.tool payload.json > /dev/null && echo ok
# Count blocks and flag over-length section text
python3 - <<'PY'
import json
b=json.load(open("payload.json"))["blocks"]
print("blocks:",len(b))
for i,blk in enumerate(b):
t=blk.get("text",{}).get("text","")
if len(t)>3000: print("over-length section at",i,len(t))
PY
# Post a single block to isolate the failure
curl -s -X POST https://slack.com/api/chat.postMessage \
-H "Authorization: Bearer $SLACK_BOT_TOKEN" -H "Content-Type: application/json" \
-d '{"channel":"C0123456789","blocks":[ONE_BLOCK]}'
Conclusion
invalid_blocks is a structure problem in your Block Kit payload, not a transient failure — the same body will always be rejected until you fix it. The usual root causes:
- A
sectionwith neithertextnorfields. - Text past a length limit (3000 for sections, 75 for buttons/labels).
- More than 50 blocks in a message (100 in a view).
- An unknown or misspelled block/element
type. - A
plain_textvsmrkdwnmismatch. - An
inputblock used in a message instead of a modal.
Read response_metadata.messages, jump to the json-pointer it gives you, enforce Block Kit limits in code, and test with real-sized data — that turns invalid_blocks from a mystery into a one-line fix.
Fixed it? Get 500 Slack & DevOps AI prompts — free
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.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
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.