Ana içeriğe geç
Developers Message cards

Design message cards

Everything a bot posts, from a webhook, a command reply or a button update, is a card. A good card says what happened in one glance: a coloured edge, a title, a status, the details underneath.

What you can do

  • Show a statusA coloured pill like Open, Merged or Passing, with lines added and removed.
  • List the detailsLabel and value rows that members can copy in one tap.
  • Write in markdownBold, inline code, code blocks, quotes and checkboxes.
  • Show that you are workingA spinner while your bot thinks, and its reasoning behind a toggle afterwards.

In the app

Four cards as members see them. Each one is a few lines of JSON.

Anatomy of a card

The parts of a card, top to bottom. Leave out what you do not need: a card with only a title is fine.

  1. Header"Message from" and a name: the webhook's name, or your community's name for a command reply. A reply can add a badge, such as the repository.
  2. Title and statusThe title, a link when you set title_url, with the status pill beside it.
  3. SubtitleA second bold line, sub_title.
  4. DescriptionThe body, in markdown.
  5. FieldsLabel and value rows, with a copy button.
  6. FooterThe time, and lines added and removed if you send them.
json
{
  "message_container": {
    "type": "embed_message",
    "badge": "acme/web",
    "color": "purple",
    "title": "Pull request #212 opened",
    "title_url": "https://github.com/acme/web/pull/212",
    "status": { "label": "Open", "color": "green", "icon": "pull_request" },
    "sub_title": "Faster search in the channel list",
    "description": "Search now runs **per keystroke** with a 120 ms debounce.",
    "fields": [
      { "field": "Author", "value": "maya" },
      { "field": "Reviewers", "value": "dani, sam" }
    ],
    "additions": 86,
    "deletions": 12,
    "files_changed": 3
  }
}

All fields

These go in message_container. A card needs a description, or a loader. Fields marked "command replies" are dropped when you post through a webhook.

FieldTypeWhat it does
typestringembed_message (the default) or system_message.
badgestringA small chip after the name in the header, such as acme/web. Command replies
avatar_urlstringAn image over the card's icon.
colorstringThe edge colour. See colours below.
titlestringThe bold first line.
title_urlstringTurns the title into a link.
sub_titlestringA second bold line under the title.
descriptionstringThe body, in markdown.
fieldsarray[{ "field": "…", "value": "…" }]: label and value rows.
image_url, image_base64stringAn image on the card.
imagesarray[{ "image_url": "…" }]: a gallery of several images.
statusobject or stringA coloured pill next to the title. See below. Command replies
additions, deletions, files_changednumberDiff stats in the footer. Command replies
loader, loader_text, loader_sub_textboolean, stringA spinner instead of the body.
thinkingstringReasoning behind a Show thinking toggle. Command replies
The description and thinking understand markdown: **bold**, _italic_, ~~strike~~, `inline code`, fenced code blocks, > quotes, - [x] checkboxes, @mentions and :emoji:.

Status and diff stats

A status pill tells the story before anyone reads the text. It sits next to the title, or in the footer when there is no title; diff stats show next to the time. Both work in command replies, and the built-in GitHub integration uses them. A webhook drops them.

json
{
  "message_container": {
    "color": "red",
    "title": "Build failed on main",
    "status": { "label": "Failing", "color": "red" },
    "description": "`search.test.js`: 2 of 212 tests failed."
  }
}
FieldTypeWhat it does
statusobject or stringA bare string is the label: "status": "Open".
status.labelstringThe pill text. Without it there is no pill.
status.colorstringgreen, purple, red, orange, yellow, blue or gray.
status.iconstringAn optional icon from the list below.
additionsnumberLines added, shown as green +86.
deletionsnumberLines removed, shown as red -12.
files_changednumberFiles touched, shown as 3 files.

Icons

ValueIconTypical use
pull_requestgit-pull-requestA pull request opened
pull_request_closedgit-pull-request-closedClosed without merging
merge, mergedgit-mergeMerged
commitgit-commitA pushed commit
issuecircle-dotAn issue opened
issue_closedcircle-checkAn issue closed
checkcircle-checkTests passed, a job succeeded

A mapping that works for GitHub

The built-in GitHub integration uses these; copy them for your own tools.

EventLabelColourIcon
Pull request openedOpengreenpull_request
DraftDraftgraypull_request
MergedMergedpurplemerged
Closed unmergedClosedredpull_request_closed
Issue openedOpengreenissue
Issue closedClosedpurpleissue_closed
Commit pushedCommitgraycommit
Tests passedPassinggreencheck
Tests failedFailingrednone

Loader and thinking

For anything that takes a moment, like an AI answer or a long job, post a card with a spinner first, then replace it with the result. The loader works from webhooks and command replies. In a command reply you can also put the model's reasoning in thinking: members see a Show thinking toggle instead of a wall of text.

assistant
System
Message from Assistant
Thinking…Reading the last 50 messages

First: the loader

assistant
System
Message from Assistant

maya: when is the standup?

Standup is at 09:30, in #daily.
Checked the pinned messages and the recurring event in #daily.

Then: the answer, reasoning folded away

json
{
  "message_container": {
    "type": "embed_message",
    "color": "blue",
    "loader": true,
    "loader_text": "Thinking…",
    "loader_sub_text": "Reading the last 50 messages"
  }
}
json
{
  "message_container": {
    "type": "embed_message",
    "color": "blue",
    "sub_title": "maya: when is the standup?",
    "description": "Standup is at **09:30**, in #daily.",
    "thinking": "Checked the pinned messages and the recurring event in #daily…"
  }
}
FieldTypeWhat it does
loaderbooleantrue shows the spinner instead of the body.
loader_textstringThe line next to the spinner, such as "Thinking…".
loader_sub_textstringA smaller line under it.
thinkingstringFolded reasoning under the description, in markdown.

To swap the loader for the answer, update the message with a new card that leaves out loader. How is on live updates.

Long descriptions

A description can be up to 50,000 bytes. Past the first 1,000, members see the start and a Show more button that loads the rest, so a long report does not flood the channel.

System messages

Set "type": "system_message" for a notice rather than a bot post: maintenance windows, policy changes, anything that speaks for the community itself. It takes the same fields and buttons.

json
{
  "message_container": {
    "type": "system_message",
    "color": "orange",
    "title": "Maintenance tonight",
    "description": "The build servers are down from 22:00 to 23:00."
  }
}

Colours

The edge colour is the fastest signal on a card. Use the same colour for the same kind of news, every time.

ColourUse it for
greenSuccess: passed, deployed, done
redFailure: failed, down, rejected
orangeA warning that needs a look
yellowWaiting on someone: approvals, questions
blueInformation, the default
purpleCode events, or something special

Build your embed

Edit the fields or the JSON payload. Both stay in sync. Watch the message render exactly as it will in a channel. This is the real webhook body; copy it when it looks right.

Presets
Buttons
Preview
Webhook body

Keep building