Back to Blog
Guide

Slack Plan and Task Card Blocks: Every Status, Update and Limit Tested

We posted a plan block with tasks in all four statuses and standalone task cards from a bot, updated them with chat.update, and probed the limits. pending works only inside a plan, and titles have no length limit we could find.

Slack Green Team
October 3, 2026
October 5, 2026
3 min read
Share:
slack api
developers
block kit
ai
agents

The task_card block shows one step an app or agent is working on, with a status icon, optional details and output, and source links. The plan block groups up to 50 such tasks under one title. Slack added both in February 2026 and changed how they look on 20 August 2026. On 3 October 2026 we posted them from a test bot in our own workspace, in Slack web, and tried every status, an update, and bad values. A standalone task_card rejects pending, while a plan accepts it. The general-purpose card and carousel blocks are tested in Slack carousel and card blocks.

The blocks we posted

import json
from lab import raw   # our helper: POSTs JSON to https://slack.com/api/<method> with the bot token

DM = "D0C6CRJSZK8"
def rt(t): return {"type": "rich_text", "elements": [{"type": "rich_text_section", "elements": [{"type": "text", "text": t}]}]}
def task(i, status, title=None, **kw):
    d = {"task_id": f"t{i}", "title": title or f"Task {i}: {status}", "status": status}
    d.update(kw); return d

tasks = [task(1, "pending", "Read the 4.53 changelog"),
         task(2, "in_progress", "Group changes by area", details=rt("Sorting 38 entries")),
         task(3, "complete", "Find breaking changes", output=rt("2 breaking changes found"),
              sources=[{"type": "url", "text": "docs.slack.dev", "url": "https://docs.slack.dev/changelog"}]),
         task(4, "error", "Fetch the GitHub release", output=rt("HTTP 404 from the release URL"))]
r = raw("chat.postMessage", {"channel": DM, "text": "617 plan test",
        "blocks": [{"type": "plan", "title": "Release summary plan", "tasks": tasks}]})

cards = [{"type": "task_card", **task(i + 10, s)} for i, s in enumerate(["pending", "in_progress", "complete", "error"])]
r2 = raw("chat.postMessage", {"channel": DM, "text": "617 cards test", "blocks": cards})
cards3 = [c for c in cards if c["status"] != "pending"]
r3 = raw("chat.postMessage", {"channel": DM, "text": "617 cards test 3", "blocks": cards3})
MessageResponse
plan with tasks in pending, in_progress, complete, error{"ok": true, "ts": "1790989727.294849"}
Four task_card blocks including pendinginvalid_blocks, [ERROR] must be a valid enum value [json-pointer:/blocks/0/status]
Three task_card blocks without pending{"ok": true, "ts": "1790989728.050639"}

The task card reference lists only in_progress, complete and error. The plan reference's own example uses pending, and Slack accepted it there.

How each status looks in Slack

Slack showed the plan collapsed to one line, "Release summary plan", with a spinner because a task was still running. Clicking the title opened it:

Slack web message from the test bot: an open plan titled "Release summary plan" with a spinner, then "Read the 4.53 changelog" with a dot, "Group changes by area" with a spinner and the detail "Sorting 38 entries", "Find breaking changes" with a dot, the output "2 breaking changes found" and a docs.slack.dev link, "Fetch the GitHub release" with a red warning triangle and "HTTP 404 from the release URL"; below, three standalone task cards: "Task 11: in_progress" with a spinner, "Task 12: complete" with a check mark and "Task 13: error" with a red triangle

What each status got:

StatusInside a planAs a standalone task_card
pendingGrey dotRejected (invalid_blocks)
in_progressSpinnerSpinner
completeGrey dot, the same as pendingCheck mark
errorRed warning triangleRed warning triangle

Inside a plan, pending and complete look the same. The plan's header icon is what tells the reader the plan is done. details showed under the title, output under that, and sources as links. Tasks with details or output got an expand arrow.

Never appear "away" on Slack again

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

Updating the plan with chat.update

We sent the same plan again with every task set to complete and a new output on the failed one:

for t in tasks:
    t["status"] = "complete"
tasks[3]["output"] = rt("Found it on the second try")
raw("chat.update", {"channel": DM, "ts": plan_ts, "text": "617 plan test",
     "blocks": [{"type": "plan", "title": "Release summary plan", "tasks": tasks}]})

The call returned {"ok": true}. The plan collapsed again, and its header spinner became a check mark. Opened, it read:

The same plan after chat.update: header "Release summary plan" with a check mark, then four tasks each with a grey dot: "Read the 4.53 changelog", "Group changes by area" with "Sorting 38 entries", "Find breaking changes" with "2 breaking changes found" and the docs.slack.dev link, and "Fetch the GitHub release" with "Found it on the second try"

So to show progress, send the full plan each time. There is no call that changes one task; chat.update replaces the whole block. chat.startStream also accepts task_update chunks, which we did not test; our chat.startStream test covers the text side.

Limits and errors

Each row is one chat.postMessage with a bot token:

What we sentResponse
plan task with status: "done"invalid_blocks, [ERROR] must be a valid enum value [json-pointer:/blocks/0/tasks/0/status]
Two tasks with the same task_idinvalid_blocks, [ERROR] task_ids must be unique [json-pointer:/blocks/0]
plan with 0 tasksok
plan with 50 tasksok
plan with 51 tasksinvalid_blocks, [ERROR] Cannot have more than 50 tasks [json-pointer:/blocks/0]
Two plan blocks in one messageinvalid_blocks, [ERROR] Cannot have more than 1 plan block
task_card without titleinvalid_blocks, [ERROR] missing required field: title [json-pointer:/blocks/0]
details as a plain stringinvalid_blocks, [ERROR] must provide an object [json-pointer:/blocks/0/details]
A source with no urlinvalid_blocks, [ERROR] missing required field: url [json-pointer:/blocks/0/sources/0]
task_card title of 3,001, 12,000, 50,000 and 200,000 charactersok every time
plan title of 3,001 charactersok

We found no title limit. Slack accepted 200,000 characters and showed the long titles in full, wrapped across the screen, so cap titles in your own code. details and output must be a single rich_text object, the same structure as the rich_text block.

We did not check the mobile app. When a block fails, our invalid_blocks guide explains how to read the json-pointer.

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

How to See All Your DMs in Slack, Including the Hidden Ones

The DMs tab lists your direct messages with people. App DMs, Slackbot and DMs with deleted accounts sit elsewhere. We compared the tab with the full list the Slack API returned.

Slack Green Team
Guide

Slack Images Not Showing? 5 Causes We Reproduced

We uploaded images to a Slack test channel and then switched each image setting off, ran /collapse, and posted from a bot that was not in the channel. Five causes, each reproduced.

Slack Green Team
Guide

How to Message Yourself on Slack (and What It Notifies)

Your own name under Direct messages opens a private DM with yourself. We sent it a note, a file, a scheduled message and a reminder, and checked which ones raised a badge.

Slack Green Team