ClaudeにSlack Block KitのJSONを作らせる手順と上限の直し方
ボタン付きメッセージとモーダルのblocks JSONをClaudeに書かせ、Block Kit Builderで見た目を確かめ、50ブロックなどの上限に当たったときに直す手順をまとめました。
Slack Block KitのJSONは、Claudeに書かせて、Block Kit Builderに貼って見た目を確かめ、上限を超えていないかを小さなスクリプトで検査する流れが堅実です。Claudeが出したJSONは構文が正しくても、文字数や個数の上限を超えることがあります。上限違反はSlack APIの invalid_blocks で初めて表に出るため、投稿前に手元で止めておくと直しが速くなります。
この記事は、メッセージ(ボタン付き)とモーダルの2種類のJSONを題材に、依頼文、確認、上限の検査、直し方の順で進めます。
Block Kitは何でできていて、Claudeに何を頼むか
Block Kitは、ブロック(blocks)、ブロック要素(block elements)、合成オブジェクト(composition objects)の3層でできています。ブロックは画面の部品で、ボタンやメニューのような対話部品はブロック要素として中に入ります。文字や選択肢を表す合成オブジェクトは、その両方の内側で使われます。
ブロックを置ける面(surface)は、Homeタブ、メッセージ、モーダルの3つです。どの面でも、用意するのは blocks 配列で、JSONを組み立てるという作り方は共通です。ただし、ブロックによっては特定の面でしか使えません。Claudeが出したブロックが目的の面で使えるかは、Block Kitのリファレンスで確かめます。
頼む内容は、次の3点を揃えると外れにくくなります。
- 投稿先の面(メッセージかモーダルか)
- 画面に出したい情報と、押せるボタンの種類
- 使ってよいブロックの範囲(
header/section/actions/divider/contextなど)
「Slackに通知を出すJSONを作って」だけの依頼では、使うブロックや面が決まらず、出力が毎回ぶれやすくなります。公式は旧形式の attachments より blocks を勧めているので、依頼文でもブロックの種類を指定しておきます。
ボタン付きメッセージのblocks JSONを作る
承認依頼を例にします。Claudeには次のように頼みます。
Slackのメッセージ用blocks JSONを作ってください。
- 先頭にheader、次にsectionで「種別」「申請者」をfieldsで2列に並べる
- 最後にactionsで「承認」(primary)と「却下」(danger)のボタン
- 各ボタンにaction_idとvalueを付け、却下には確認ダイアログを付ける
- 使うのはBlock Kitのblocksだけ。attachmentsは使わない
- 先頭のtextフォールバックも別に出す返ってくるJSONは、たとえば次のような形になります。これは公式のリファレンスに沿って組んだ例で、Claudeの実出力ではありません。
{
"text": "新しい申請: 有給休暇",
"blocks": [
{
"type": "header",
"text": { "type": "plain_text", "text": "新しい申請" }
},
{
"type": "section",
"fields": [
{ "type": "mrkdwn", "text": "*種別:*\n有給休暇" },
{ "type": "mrkdwn", "text": "*申請者:*\n山田" }
]
},
{
"type": "actions",
"elements": [
{
"type": "button",
"text": { "type": "plain_text", "text": "承認" },
"style": "primary",
"action_id": "approve",
"value": "req_123"
},
{
"type": "button",
"text": { "type": "plain_text", "text": "却下" },
"style": "danger",
"action_id": "reject",
"value": "req_123",
"confirm": {
"title": { "type": "plain_text", "text": "却下しますか" },
"text": { "type": "plain_text", "text": "この操作は申請者に通知されます" },
"confirm": { "type": "plain_text", "text": "却下する" },
"deny": { "type": "plain_text", "text": "戻る" }
}
}
]
}
]
}見るべき点は4つです。
headerの文字はplain_textの指定が前提。mrkdwnは使わないfieldsは2列で並ぶ。textを持つsectionとは別の使い方になる- ボタンの
styleでprimaryを付けてよいのは、1組のうち1個だけ。承認と却下を並べるなら、primaryは承認側だけにして、却下はdangerにする - ボタンを押したときに届くのは
action_idとvalue。この2つが受け取り側のBolt側コードと対応する
最上位の text は、通知に出るフォールバック文字列です。ブロックを使うメッセージでは必須ではありませんが、付けることが勧められています。スクリーンリーダーは最上位の text を読み、内側のブロックの中身は読まないためです。text を省くと、Slackがブロックから読み上げ用の文章を組み立てます。アクセシビリティを意識するなら、text に必要な内容を全部入れるか、text を省くかのどちらかを選びます。
Block Kit Builderで見た目を確かめる
JSONはBlock Kit Builderに貼って、実際の表示で確かめます。Builderはブロックを選び、設定し、プレビューできる試作用の場です。ドラッグしてブロックを並べ替えることもできます。
確認の順番は次のとおりです。
BuilderでClaudeのJSONを確かめる順番
- 1
blocks配列だけを貼る
メッセージの場合は、最上位の
blocks配列を貼ります。textやchannelはブロックの外側の項目なので、貼るのはblocksの中身です。 - 2
表示の崩れを見る
fieldsの2列、ボタンの色、見出しの長さを目で確かめます。ここで崩れていれば、依頼文に直したい点を足して作り直します。 - 3
対象の面を切り替える
メッセージ用に作ったJSONでも、モーダルやHomeタブで使えないブロックがあります。使う面でプレビューします。
- 4
手元の検査スクリプトにかける
見た目が合っても、上限は別です。次の節のスクリプトで個数と文字数を検査します。
Builderで確かめられるのは見た目と構造で、上限の検査までは行いません。上限は公式のリファレンスに書かれた数字なので、次の節のとおり自分で検査を用意します。
モーダルのview JSONを作る
モーダルは、メッセージとは別の view オブジェクトで組みます。頼むときは、モーダルであることと入力欄の種類を明示します。
Slackのモーダル用viewのJSONを作ってください。
- type は modal、callback_id は "request-form"
- タイトルは24文字以内、送信ボタンの文言も付ける
- inputブロックで「件名」(1行)と「詳細」(複数行)
- 値の取り出し先が分かるよう、block_idとaction_idを明示するモーダルには、メッセージにない決まりがいくつかあります。
| 項目 | 内容 |
|---|---|
title | 内容plain_text、24文字以内 |
submit / close | 内容plain_text、それぞれ24文字以内。input ブロックを使うなら submit は必須 |
callback_id | 内容255文字以内。送信を受け取るときの識別子 |
private_metadata | 内容3000文字以内。画面に出ず、送信時にアプリへ戻ってくる |
blocks | 内容100個以内 |
特に踏みやすいのは、submit の付け忘れとタイトルの長さです。日本語のタイトルは気づかないうちに24文字を超えるので、検査対象に入れておきます。
モーダルを開くには、ユーザー操作から届く trigger_id が要ります。trigger_id は送られてから3秒で失効するため、ボタンやslashコマンドを受けたら、すぐに views.open を呼ぶ必要があります。送信を受けた側も、3秒以内に応答します。ここはBlock KitのJSONではなく受け取り側のコードの話なので、Boltでの組み立てはClaude CodeでSlack Botを作るにまとめています。
上限に当たったときの直し方
Claudeに「できるだけ詳しく」と頼むと、ブロックが増えます。リファレンスに載っている主な上限は次のとおりです。
| 対象 | 上限 |
|---|---|
| メッセージのブロック数 | 上限50 |
| モーダル・Homeタブのブロック数 | 上限100 |
section の text | 上限3000文字 |
section の fields | 上限10個、各2000文字 |
header の文字 | 上限150文字 |
actions の要素 | 上限25個 |
context の要素 | 上限10個 |
| ボタンの文言 | 上限75文字(表示は約30文字で切れることがある) |
ボタンの value | 上限2000文字 |
block_id / action_id | 上限255文字 |
この表を毎回目で照合するのは現実的ではないので、検査はスクリプトに任せます。次は、上の表を実装したPythonの例です。
import json, sys
def tlen(o):
return len(o.get("text", "")) if isinstance(o, dict) else 0
def check(blocks, surface="message"):
errs = []
limit = 50 if surface == "message" else 100
if len(blocks) > limit:
errs.append(f"blocks: {len(blocks)} 個 (上限 {limit})")
for i, b in enumerate(blocks):
t, p = b.get("type"), f"blocks[{i}]({b.get('type')})"
if t == "section":
if tlen(b.get("text")) > 3000:
errs.append(f"{p}.text が {tlen(b['text'])} 文字 (上限 3000)")
if len(b.get("fields", [])) > 10:
errs.append(f"{p}.fields が 10 個超")
elif t == "header":
if tlen(b.get("text")) > 150:
errs.append(f"{p}.text が {tlen(b['text'])} 文字 (上限 150)")
elif t == "actions":
if len(b.get("elements", [])) > 25:
errs.append(f"{p}.elements が 25 個超")
return errs
data = json.load(open(sys.argv[1]))
blocks = data["blocks"] if isinstance(data, dict) else data
errs = check(blocks, sys.argv[2] if len(sys.argv) > 2 else "message")
print("\n".join(errs) if errs else "OK: 検査した上限に収まっています")
sys.exit(1 if errs else 0)掲載したのは要点を絞った版です。手元では fields の各2000文字、ボタンの文言とvalue、context の個数、block_id の長さまで同じ形で足しました。ここで使った値は、いずれもリファレンスの記述が出典です。
実際に動かした結果を載せます。承認依頼のJSONは通り、見出しを長くして30件のチケットを section と divider で並べたJSONは落ちました。Python 3.12.2で動かしています。
python3 check_blocks.py good.json
# OK: 検査した上限に収まっています
python3 check_blocks.py bad.json
# blocks: 61 個 (上限 50)
# blocks[0](header).text が 200 文字 (上限 150)検査が落ちたら、エラー行をそのままClaudeに返して直させます。直し方は原因で分かれます。
ブロック数が50を超えるとき
30件の項目を section と divider の1組ずつで並べると、61個になって上限を超えます。次のどれかで減らします。
dividerを外す。区切り線だけで半分のブロックを使っているケースが多い- 1件を1ブロックにまとめ、
sectionのfieldsに項目を寄せる(1ブロックに最大10個) - 先頭の数件だけメッセージに出し、続きはスレッドへの返信として別に投稿する(
chat.postMessageのthread_tsを使う) - 全件を見たい人向けに「一覧を開く」ボタンを置き、モーダルに出す。モーダルは100ブロックまで入る
モーダルを逃がし先にする案は、全件をチャンネルに流したくないときに向いています。ただしモーダルを開くには trigger_id が要るので、ボタンを押した反応としてしか出せません。
文字数が上限を超えるとき
header は150文字、ボタンは75文字、section の本文は3000文字です。Claudeに「見出しは全角30字程度、ボタンは10字以内」のように、上限よりずっと低い目安を伝えておくと、直しの往復が減ります。長い本文は、要点だけを section に置き、詳細は context や別メッセージに回します。
invalid_blocks が返ってきたとき
Slack APIは、ブロックが正しくないときに invalid_blocks を、JSONの形式やBlock Kitの構文に合わないときに invalid_blocks_format を返します。エラー名だけでは、どのブロックのどの値が悪いのかは分かりません。投稿前の検査で個数と文字数を潰し、それでも出るときはBuilderでブロックを1つずつ減らして切り分けます。
Claude Codeに検査まで回させる
JSONを作る場所がClaude Codeなら、検査の反復もまかせられます。プロジェクトの CLAUDE.md に規約を書いておきます。
## Slack Block Kit
- blocks JSONは `slack/blocks/*.json` に置く
- 書いたら必ず `python3 scripts/check_blocks.py <file> message` を実行する
- 失敗したら出力の行を読んで直し、通るまで繰り返す
- headerは150文字以内、ボタン文言は10文字以内を目安にする
- attachmentsは使わないこうしておくと、Claudeは書く、検査を実行する、出力を読んで直す、の順に回します。人が行数を数える必要はありません。検査に使うコマンドは、権限設定で事前に許可しておくと確認が減ります。
反復の最後に人が見るのは、Builderで開いた表示の確認だけです。個数や文字数の検査は機械で、見た目の良し悪しは目で、と役割を分けます。
受け取り側とつなぐときの注意
Block KitのJSONを作るのは出力の半分で、もう半分はボタンが押されたあとの処理です。ボタンを置いた時点で、アプリはユーザーの操作を受け取る責任を負います。押されたときのペイロードには action_id と value が入るので、Claudeに依頼文を書くときにこの2つの命名規則を決めておくと、受け取り側のコードと突き合わせやすくなります。たとえば approve / reject のように動詞で揃え、value には申請IDだけを入れます。
Boltで受け取る構成はClaude CodeでSlack Botを作る、Agent SDKで常駐させる構成はAgent SDKでSlack常駐botを作るが手がかりになります。SlackにClaude自体を入れて使う場合の違いは、ClaudeとSlackの連携で整理しています。
まとめ
Claudeに頼む価値が大きいのは、section と fields の組み合わせ、ボタンと確認ダイアログ、モーダルの input のような、フィールドが多くて書き間違えやすい部分です。JSONの検証は、見た目をBuilder、上限を検査スクリプトに分けると、invalid_blocks を投稿前に潰せます。
上限に当たったときは、まず divider を外し、次に fields へ寄せ、それでも足りなければスレッドかモーダルへ逃がします。最初の依頼文に上限より低い目安を入れておくと、直しの往復そのものが減ります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。