Slack invalid_blocks: Every Block Kit Limit We Hit, With the Error
We sent Block Kit messages just under and just over each limit through chat.postMessage and an incoming webhook on 1 October 2026. The last value that worked, the first that failed, and the exact error text for each.
On this page
invalid_blocks means Slack rejected your blocks array because one block broke a Block Kit rule, and nothing was posted. The Web API tells you which rule and where, in response_metadata.messages. An incoming webhook returns only the words invalid_blocks with HTTP 400. We sent each case below on 1 October 2026 through chat.postMessage with a bot token and through an incoming webhook, in a test workspace. Both paths enforced the same limits. A Home tab view allows 100 blocks, which we checked in the App Home tab test.
Reading the error
From chat.postMessage with 51 section blocks:
{
"ok": false,
"error": "invalid_blocks",
"errors": [
"no more than 50 items allowed [json-pointer:/blocks]"
],
"response_metadata": {
"messages": [
"[ERROR] no more than 50 items allowed [json-pointer:/blocks]"
]
}
}
The json-pointer is the path to the bad part: /blocks/0/text/text means the text field inside the text object of the first block. Slack numbers blocks from 0. slack_sdk raises SlackApiError; print the messages to see them:
import os
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
client = WebClient(token=os.environ["SLACK_BOT_TOKEN"])
blocks = [{"type": "header", "text": {"type": "plain_text", "text": "h" * 151}}]
try:
client.chat_postMessage(channel=os.environ["CHANNEL"], text="Deploy report", blocks=blocks)
except SlackApiError as e:
print(e.response["error"])
for line in e.response["response_metadata"]["messages"]:
print(line)
Output:
invalid_blocks
[ERROR] failed to match all allowed schemas [json-pointer:/blocks/0/text]
[ERROR] must be less than 151 characters [json-pointer:/blocks/0/text/text]
The webhook gives you none of this. When a webhook says invalid_blocks, paste the body into our Block Kit checker: it shows the lines chat.postMessage would return, without sending anything, and builds a payload that passes. Sending the same blocks once through chat.postMessage, or pasting them into Block Kit Builder, also finds the field.
The limits we measured
| Rule | Last value that worked | First that failed | Error message |
|---|---|---|---|
| Blocks per message | 50 | 51 | no more than 50 items allowed [json-pointer:/blocks] |
Section text | 3,000 characters | 3,001 | must be less than 3001 characters |
Header text | 150 characters | 151 | must be less than 151 characters |
| Button label | 75 characters | 76 | must be less than 76 characters |
| Elements in one actions block | 25 | 26 | no more than 25 items allowed |
| Elements in a context block | 10 | 11 | no more than 10 items allowed |
Section fields | 10 | 11 | no more than 10 items allowed [json-pointer:/blocks/0/fields] |
How Slack draws headers, context rows and actions blocks with 7 or more buttons (they collapse behind a "+ N more" button) is on the context block page, and card and carousel limits are on the carousel block page.
Fifty sections of 3,000 characters each, 150,000 characters in one message, posted without an error. We found no separate total for blocks up to that size. The 40,000-character figure people quote is for the top-level text, and long text behaves differently: our chat.update test found chat.postMessage splits it into messages of 4,000 characters. Keep text short and in line with the blocks: desktop notifications read the blocks first and mobile push reads text, as our notification test shows.
Structure errors
| What we sent | Error message |
|---|---|
"type": "paragraph" | unsupported type: paragraph [json-pointer:/blocks/0/type] |
A section with no text and no fields | must define either text or fields (Slack wraps text and fields in backticks) |
A section with text: "" | must be more than 0 characters |
A header with mrkdwn text | must be a valid enum value [json-pointer:/blocks/0/text/type] |
Two buttons with the same action_id | action_id "same" already exists [json-pointer:/blocks/0/elements/1/action_id], with action_id in backticks |
| An image block whose URL returns 404 | downloading image failed [json-pointer:/blocks/0/image_url] |
blocks as a string that is not JSON | invalid_blocks_format, not invalid_blocks |
A button without an action_id posted fine. Slack fills one in. The duplicate check runs within the message, so give each button its own action_id when you build them in a loop. Headers take only plain_text. Button styles have the same enum check: anything but primary or danger fails, as our button style test shows, and the date picker's format errors are on our datepicker test.
The image error comes from Slack fetching the URL when you post; our test URL returned 404. Use a URL Slack can download without a login. SVG files, HTML pages and servers slower than about 8 seconds gave the same error in our image block test.
Never appear "away" on Slack again
Cloud-based. No downloads. Works 24/7 even when your laptop is off.
Fixing it in Block Kit Builder
Builder runs the same checks before you send. Our 152-character header showed this:
The banner only says "failed to match all allowed schemas". The red markers next to lines 5 and 7 point at the header's text, and the preview drops the broken block. The API's error text named the limit; Builder did not.
Text and blocks together
Always send text next to blocks. In our run, chat.postMessage with blocks and no text posted without a warning and copied the section text into text. Slack's docs say notifications use text, so write it yourself instead of relying on the copy. Formatting rules inside mrkdwn text are on the Block Kit markdown page, and chart blocks, which have their own limits, are on Block Kit charts.
FAQ
Why does my webhook return invalid_blocks when the same JSON works in Builder?
Check the shape of the body first. We sent the wrong shapes through a webhook: a bare [...] array returned no_text, and {"blocks": {"blocks": [...]}} returned invalid_blocks_format. The body must be {"blocks": [...]} at the top level, which is the shape Builder's payload tab shows. If the shape is right, a 400 invalid_blocks is one of the limits in the tables above.
What is the limit for a section block's text?
3,000 characters. 3,001 failed in our test, through the API and the webhook.
Stop Jiggling Your Mouse.
Join hundreds of remote workers who never worry about their Slack status. Set it up once, stay green forever.
Related Articles
Slack Bot Scopes: The Scope Each API Method Needs, Measured on 44 Methods
We gave a Slack bot one scope, called 44 common Web API methods, and read the needed field of every missing_scope error. Then we added the scopes and called them again. The table shows which scope clears each method.
Slack File Size Limit: 1 GB Is Exactly 1,073,741,824 Bytes, Tested
We uploaded files of 50 MB, exactly 1 GiB and 1 GiB plus one byte to Slack through its upload API. The limit is 1,073,741,824 bytes, and a failed upload can still leave a broken file behind.
Slack Dark Mode: Where the Setting Is, and Which Custom Emoji Disappear
Slack's dark mode is under Preferences > Appearance > Color Mode, with Light, Dark and System. We switched modes in Slack web with three test emoji and measured which ones vanish.