Back to Blog
Guide

Slack Message Metadata: What Gets Stored, Dropped and Sent, Tested

Message metadata is a hidden JSON object an app attaches to a message. We posted it with chat.postMessage, read it back, grew it past the limit and logged every event. Bad metadata does not fail the call; Slack drops it and posts the message anyway.

Slack Green Team
October 4, 2026
October 4, 2026
4 min read
Share:
slack api
slack messages

Slack message metadata is a small JSON object, an event_type plus an event_payload, that an app attaches to a message it posts. People in the channel never see it. Other code reads it back through the API or receives it as a message_metadata_posted event. We tested it on 4 October 2026 with a throwaway app in a free-plan workspace, using a bot token with chat:write, channels:history and metadata.message:read, and a Socket Mode listener for the events.

Posting a message with metadata

Send metadata next to text in chat.postMessage. With a JSON body it is an object. With a form-encoded body it is a JSON string; both worked in our test:

{
  "channel": "C0C6MGP65BK",
  "text": "Order A-1042 received",
  "metadata": {
    "event_type": "sglab_order_created",
    "event_payload": {"order_id": "A-1042", "amount": 49.0, "currency": "USD", "items": 2}
  }
}

The response echoed the metadata back with one change: 49.0 came back as 49. Slack stores numbers as JSON numbers, so do not rely on a trailing .0 to mark a float.

The posted message in Slack's web app: the bot name and the line Order A-1042 shipped, edited text only, with no sign of the metadata

That is everything a person sees. There is no icon, no "details" view and nothing in the message menu, even after the metadata was updated twice.

Reading it back: include_all_metadata

We read the same message with conversations.history twice, with the bot that posted it:

Callmetadata in the response
No extra argument{"event_type": "sglab_order_created"} only
include_all_metadata=trueevent_type and the full event_payload

So without the flag you get only the type, even for metadata your own app wrote. conversations.replies behaved the same way. The logged-in web client session returned the same two shapes. A message shortcut payload is the exception: when we ran an app shortcut on the message, the message_action payload carried the full metadata, payload included, with no flag.

Bad metadata does not fail the call

Every mistake we made still returned ok: true and posted the message. Slack dropped the metadata and put a warning in response_metadata:

What we sentMessage posted?Metadata kept?Warning
event_type with a space or a dotyesnoinvalid_metadata_schema, "invalid event name"
Empty event_typeyesno"invalid event name"
No event_payloadyesno"missing required field: event_payload"
event_payload as an array or a stringyesno"must provide an object"
Form field that is not valid JSONyesnoinvalid_metadata_format, "Failed to parse JSON"
Payload over 4,000 bytesyesnometadata_too_large
event_type of 101 to 139 charactersyesno"invalid event name"
event_type of 178 or 256 charactersnointernal_error

Upper case (Order_Created), dashes (order-created), digits, nested objects, arrays inside the payload, true and null were all accepted. The longest event_type that was kept was 100 characters. If your code only checks ok, it will never notice that the metadata is gone, so check response_metadata.warnings after every post that carries metadata.

Never appear "away" on Slack again

Cloud-based. No downloads. Works 24/7 even when your laptop is off.

The 4,000-byte limit, measured

We grew one string field until the metadata was dropped. The warning text gives the size Slack counted: "The size of the message's metadata is 4001 bytes. The total size of a message's metadata should not exceed 4000 bytes."

  • • The whole metadata object counts, not just event_payload. Our wrapper {"event_type":"sglab_order_created","event_payload":{"blob":""}} is 64 bytes, and a 3,936-character blob (4,000 total) was kept while 3,937 was dropped.
  • • Non-ASCII text counts six bytes per character. 1,400 Korean characters were counted as 8,464 bytes, which is 64 plus 1,400 times 6. Slack measures the JSON with each character escaped as \uXXXX. Keep IDs in the payload and look up long text elsewhere.

The events your app receives

Subscribing to message_metadata_posted needs metadata.message:read and a metadata subscription in the manifest that names the event type. Ours was {"app_id": "*", "event_type": "sglab_order_created"}. Then we posted, edited and deleted messages and timed the events from our call:

ActionEvents, in arrival order
chat.postMessage with metadatamessage at 0.70 s, message_metadata_posted at 0.72 s
chat.update with new metadatamessage_metadata_updated at 0.76 s, message_changed at 0.77 s
chat.deletemessage_deleted at 0.74 s, message_metadata_deleted at 0.82 s

The updated and deleted events include previous_metadata, so you can see what changed without storing it yourself. Messages whose event_type was not in the subscription (order_created_2, order-created) sent no metadata event at all, though the plain message event still arrived. A chat.update with new text and no metadata field kept the old metadata.

For the other message events in that table, see our Slack Events API event types page and the message_changed event test.

FAQ

Can a user see or edit message metadata?

No. Nothing in Slack's web app shows it, and there is no field for it in the message menu. We changed it only through chat.update from the bot that posted the message. Reading is wider: the owner's own web session read the full payload with include_all_metadata=true, so do not put secrets in it.

Is message metadata the same as Work Objects?

No. Event metadata is the event_type and event_payload shape on this page. Work Objects use the same metadata field with an entities array instead, which is why our bad-metadata warnings listed errors for both shapes. Our Slack Work Objects test covers the entity form.

Always Active

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

Guide

Slack App Shortcuts: Where Global and Message Shortcuts Appear, Tested

Slack apps can add two kinds of shortcut: global ones in the composer's shortcut list and message ones in a message's menu. We added one of each, found where Slack puts them, logged both payloads and recorded what users see when the app does not answer.

Slack Green Team
Guide

Slack Datepicker, Timepicker and Datetimepicker: Payloads and Time Zones

We posted Slack's datepicker, timepicker and datetimepicker in a message and a modal, picked values and logged every payload. The datetimepicker returns a Unix timestamp; the timepicker returns a bare HH:mm with no zone unless you set one.

Slack Green Team
Guide

Slack Button Style: Primary, Danger, URL Buttons and Their Limits

A Slack Block Kit button has three looks: no style, primary (green) and danger (red). We posted every kind, clicked each one, logged the block_actions payloads and recorded the errors for colors, disabled buttons and long labels.

Slack Green Team