Sending Messages
slackblocks produces the JSON payloads that Slack APIs accept; it does not make HTTP calls itself. Send those payloads with the established Slack client for your language or any HTTP client.
The trick is the ** operator: a slackblocks.Message is a mapping, so you can unpack it directly into client.chat_postMessage(...).
With the modern slack-sdk
- Python (slackblocks)
- JSON Message
- Equivalent `curl`
- Slack UI
from os import environ
from slack_sdk import WebClient
from slackblocks import Message, SectionBlock
client = WebClient(token=environ["SLACK_API_TOKEN"])
block = SectionBlock("Hello, world!")
message = Message(channel="#general", blocks=block)
response = client.chat_postMessage(**message)
{
"channel": "#general",
"mrkdwn": true,
"blocks": [
{
"type": "section",
"block_id": "992ceb6b-9ad4-496b-b8e6-1bd8a632e8b3",
"text": {
"type": "mrkdwn",
"text": "Hello, world!"
}
}
]
}
Note: the block_id field is a pseudorandomly generated UUID. Pass an explicit block_id to any block constructor if you need deterministic IDs (e.g. for testing or interaction handling).
curl -H "Content-type: application/json" \
--data '{"channel":"#general","blocks":[{"type":"section","block_id":"992ceb6b-9ad4-496b-b8e6-1bd8a632e8b3","text":{"type":"mrkdwn","text":"Hello, world!"}}]}' \
-H "Authorization: Bearer ${SLACK_API_TOKEN}" \
-X POST https://slack.com/api/chat.postMessage

With the legacy slackclient
The API is identical — only the import path changes:
from os import environ
from slack import WebClient # legacy slackclient package
from slackblocks import Message, SectionBlock
client = WebClient(token=environ["SLACK_API_TOKEN"])
message = Message(channel="#general", blocks=SectionBlock("Hello, world!"))
response = client.chat_postMessage(**message)
Other delivery surfaces
slackblocks provides specialized message classes for each Slack delivery surface. They all unpack the same way as Message.
Incoming webhooks
from slack_sdk.webhook import WebhookClient
from slackblocks import WebhookMessage, SectionBlock
webhook = WebhookClient(url="https://hooks.slack.com/services/...")
message = WebhookMessage(blocks=SectionBlock("Build complete :white_check_mark:"))
webhook.send(**message)
WebhookMessage supports webhook-only options like response_type, replace_original, and delete_original.
Slash command / interaction responses
When responding to a slash command or interactive payload, use MessageResponse:
from slackblocks import MessageResponse, SectionBlock
response_body = MessageResponse(
blocks=SectionBlock("Got it! Working on that now..."),
ephemeral=True, # only visible to the invoking user
).json()
Modals & home tabs
For modals and home tab views, build a Modal (or HomeTabView) and pass it to views_open / views_publish:
from slack_sdk import WebClient
from slackblocks import Modal, SectionBlock
client = WebClient(token=environ["SLACK_API_TOKEN"])
modal = Modal(
title="Confirm action",
blocks=[SectionBlock("Are you sure?")],
submit="Yes",
close="Cancel",
)
client.views_open(trigger_id=trigger_id, view=modal.to_dict())
See Modals reference and Views reference for the full surface.
Sending without an SDK
Because Message renders to a plain dict, you can also send it directly:
import json
import os
import urllib.request
from slackblocks import Message, SectionBlock
message = Message(channel="#general", blocks=SectionBlock("Hello, world!"))
req = urllib.request.Request(
"https://slack.com/api/chat.postMessage",
data=message.json().encode("utf-8"),
headers={
"Content-Type": "application/json; charset=utf-8",
"Authorization": f"Bearer {os.environ['SLACK_API_TOKEN']}",
},
)
urllib.request.urlopen(req)
With @slack/web-api
.build() returns a plain Slack-shaped object, so pass the payload directly to the official client:
import { WebClient } from "@slack/web-api";
import { Message, SectionBlock } from "@nicklambourne/slackblocks";
const client = new WebClient(process.env.SLACK_API_TOKEN);
const payload = Message()
.channel("C0123456")
.blocks(SectionBlock().text("Hello, world!"))
.build();
await client.chat.postMessage(payload);
Use a channel ID rather than a display name when possible. Provide a text fallback for notifications and accessibility in production messages.
Incoming webhooks
import { IncomingWebhook } from "@slack/webhook";
import { SectionBlock, WebhookMessage } from "@nicklambourne/slackblocks";
const webhook = new IncomingWebhook(process.env.SLACK_WEBHOOK_URL!);
await webhook.send(
WebhookMessage()
.blocks(SectionBlock().text("Build complete :white_check_mark:"))
.build(),
);
Slash-command and interaction responses
Construct the response body, then return it through your web framework:
import { MessageResponse, SectionBlock } from "@nicklambourne/slackblocks";
const responseBody = MessageResponse()
.blocks(SectionBlock().text("Got it! Working on that now..."))
.responseType("ephemeral")
.build();
Modals and home tabs
import { Modal, SectionBlock } from "@nicklambourne/slackblocks";
const view = Modal()
.title("Confirm action")
.blocks(SectionBlock().text("Are you sure?"))
.submit("Yes")
.close("Cancel")
.build();
await client.views.open({ trigger_id: triggerId, view });
Sending without an SDK
await fetch("https://slack.com/api/chat.postMessage", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.SLACK_API_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify(payload),
});
With slack-go/slack
slackblocks block builders implement slack.Block, so they fit directly into the native options accepted by github.com/slack-go/slack. Keep the channel, fallback text, threading, unfurling, and other delivery options in slack-go; use slackblocks for the Block Kit content.
package example
import (
"context"
"os"
slackblocks "github.com/nicklambourne/slackblocks/go/v2"
slack "github.com/slack-go/slack"
)
func send(ctx context.Context) (string, error) {
client := slack.New(os.Getenv("SLACK_API_TOKEN"))
_, timestamp, err := client.PostMessageContext(
ctx,
"C0123456",
slack.MsgOptionText("Hello from slackblocks!", false),
slack.MsgOptionBlocks(
slackblocks.NewSectionBlock().Text("Hello, world!"),
),
)
return timestamp, err
}
slack-go marshals each builder while applying MsgOptionBlocks. That runs slackblocks validation before any HTTP request is made and preserves newer block types and fields even when the pinned slack-go release does not model them itself.
Higher-level components expand to the same native type:
blocks, err := slackblocks.NewPaginator().
Blocks(
slackblocks.NewSectionBlock().Text("Result one"),
slackblocks.NewSectionBlock().Text("Result two"),
).
ActionIDPrefix("results").
PageSize(1).
SlackBlocks()
if err != nil {
return err
}
_, _, err = client.PostMessageContext(
ctx,
"C0123456",
slack.MsgOptionBlocks(blocks...),
)
Incoming webhooks
Use slack-go's webhook envelope and put slackblocks builders in its Blocks collection:
message := &slack.WebhookMessage{
Text: "Build complete",
Blocks: &slack.Blocks{BlockSet: []slack.Block{
slackblocks.NewSectionBlock().Text("Build complete :white_check_mark:"),
}},
}
return slack.PostWebhookContext(ctx, os.Getenv("SLACK_WEBHOOK_URL"), message)
Slash-command and interaction responses
Use NewMessageResponse() for the immediate JSON body returned by a slash-command or interaction endpoint. Build() returns an Object, so encode it with encoding/json and return it through your web framework.
Modals and App Home
Use slack-go's native view request types for the view envelope and slackblocks builders for its blocks:
modal := slack.ModalViewRequest{
Type: slack.VTModal,
Title: slack.NewTextBlockObject("plain_text", "Confirm action", false, false),
Submit: slack.NewTextBlockObject("plain_text", "Yes", false, false),
Close: slack.NewTextBlockObject("plain_text", "Cancel", false, false),
Blocks: slack.Blocks{BlockSet: []slack.Block{
slackblocks.NewSectionBlock().Text("Are you sure?"),
}},
}
_, err = client.OpenViewContext(ctx, triggerID, modal)
For App Home, place the same builders in slack.HomeTabViewRequest:
home := slack.HomeTabViewRequest{
Type: slack.VTHomeTab,
Blocks: slack.Blocks{BlockSet: []slack.Block{
slackblocks.NewSectionBlock().Text("Welcome home!"),
}},
}
_, err = client.PublishViewContext(ctx, slack.PublishViewContextRequest{
UserID: userID,
View: home,
})
Sending without a client library
If an application deliberately does not use slack-go, NewMessage().Build() still returns an ordinary JSON-compatible payload. Set its channel and delivery fields there, marshal it with encoding/json, and post it with net/http or another client.
With the official Slack Java SDK
Completed slackblocks block values implement the Slack SDK's LayoutBlock interface directly. Keep the channel, fallback text, threading, unfurling, and other delivery options on ChatPostMessageRequest; no adapter is involved.
import com.slack.api.Slack;
import com.slack.api.methods.MethodsClient;
import com.slack.api.methods.request.chat.ChatPostMessageRequest;
import io.github.nicklambourne.slackblocks.block.SectionBlock;
import java.util.List;
MethodsClient client = Slack.getInstance().methods(System.getenv("SLACK_API_TOKEN"));
SectionBlock block = SectionBlock.builder().markdownText("Hello, world!").build();
var response = client.chatPostMessage(ChatPostMessageRequest.builder()
.channel("C0123456")
.text("Hello from slackblocks!")
.blocks(List.of(block))
.build());
Validation runs in build() before the request is created. Use a channel ID where possible and always supply a short text fallback for notifications and accessibility.
Import slackblocks classes by name, because the SDK defines classes with the same simple names in com.slack.api.model.block.
Higher-level components produce the same native block values. List.copyOf widens their List<Block> result to the List<LayoutBlock> the SDK expects:
import io.github.nicklambourne.slackblocks.block.Block;
import io.github.nicklambourne.slackblocks.component.Paginator;
import java.util.List;
List<Block> pages = Paginator.builder("search-results")
.blocks(
SectionBlock.builder().markdownText("Result one").build(),
SectionBlock.builder().markdownText("Result two").build())
.pageSize(1)
.build();
client.chatPostMessage(ChatPostMessageRequest.builder()
.channel("C0123456")
.text("Search results")
.blocks(List.copyOf(pages)) // widens List<Block> to List<LayoutBlock>
.build());
Incoming webhooks
Use the official SDK's webhook envelope and put slackblocks values directly in its blocks list:
import com.slack.api.webhook.Payload;
var payload = Payload.builder()
.text("Build complete")
.blocks(List.of(SectionBlock.builder()
.markdownText("Build complete :white_check_mark:")
.build()))
.build();
Slack.getInstance().send(System.getenv("SLACK_WEBHOOK_URL"), payload);
Slash-command and interaction responses
MessageResponse builds the JSON body returned by your web framework:
import io.github.nicklambourne.slackblocks.payload.MessageResponse;
import io.github.nicklambourne.slackblocks.payload.ResponseType;
String responseBody = MessageResponse.builder()
.blocks(SectionBlock.builder().markdownText("Got it! Working on that now...").build())
.responseType(ResponseType.EPHEMERAL)
.build()
.toJson();
Modals and App Home
Use the Slack Java SDK for the request and view envelope, and slackblocks for its Block Kit contents. This keeps trigger IDs and view lifecycle calls on the SDK while preserving the same fluent, validated block construction.
Sending without a client library
When an application intentionally does not use the Slack Java SDK client, serialize a completed MessagePayload with SlackblocksJson.write(...) and send the resulting JSON through its existing HTTP stack.
With the Slack Web API
slackblocks builds and validates the payload; HttpClient sends it. Put the channel, fallback text, blocks, and other delivery options on a MessagePayload, then post its JSON to chat.postMessage with a bot token:
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json.Nodes;
using Slackblocks.Blocks;
using Slackblocks.Payloads;
var message = new MessagePayload(
"C0123456",
text: "Hello from slackblocks!",
blocks: [new SectionBlock(text: "Hello, world!")]);
using var http = new HttpClient();
using var request = new HttpRequestMessage(HttpMethod.Post, "https://slack.com/api/chat.postMessage")
{
Content = new StringContent(message.ToJson(), Encoding.UTF8, "application/json"),
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", Environment.GetEnvironmentVariable("SLACK_BOT_TOKEN"));
using var response = await http.SendAsync(request);
var result = JsonNode.Parse(await response.Content.ReadAsStringAsync())!;
if (result["ok"]?.GetValue<bool>() != true)
{
throw new InvalidOperationException($"Slack rejected the message: {result["error"]}");
}
Validation runs in each constructor, before the request exists. Use a channel ID where possible and always supply a short text fallback for notifications and accessibility.
Higher-level components return ordinary blocks, so their results spread straight into the blocks collection:
using Slackblocks.Blocks;
using Slackblocks.Components;
using Slackblocks.Payloads;
var results = Paginator.Create(
"search-results",
[new SectionBlock(text: "Result one"), new SectionBlock(text: "Result two")],
pageSize: 1);
var message = new MessagePayload("C0123456", text: "Search results", blocks: [.. results]);
Incoming webhooks
Post a WebhookMessage to the webhook URL. Keep that URL secret, because anyone holding it can post to the channel:
using System.Text;
using Slackblocks.Blocks;
using Slackblocks.Payloads;
var payload = new WebhookMessage(
text: "Build complete",
blocks: [new SectionBlock(text: "Build complete :white_check_mark:")]);
using var http = new HttpClient();
using var content = new StringContent(payload.ToJson(), Encoding.UTF8, "application/json");
using var response = await http.PostAsync(Environment.GetEnvironmentVariable("SLACK_WEBHOOK_URL"), content);
response.EnsureSuccessStatusCode();
Slash-command and interaction responses
MessageResponse builds the JSON body your web framework returns:
using Slackblocks.Blocks;
using Slackblocks.Payloads;
string responseBody = new MessageResponse(
blocks: [new SectionBlock(text: "Got it! Working on that now...")],
responseType: ResponseType.Ephemeral).ToJson();
Modals and App Home
ModalView and HomeTabView build the view object for views.open, views.update, views.push, and views.publish. Serialize the view into the request body alongside its trigger ID or user ID:
using System.Text.Json;
using Slackblocks.Blocks;
using Slackblocks.Payloads;
var view = new ModalView(
"Deploy",
blocks: [new SectionBlock(text: "Deploy *482* to production?")],
submit: "Deploy",
close: "Cancel");
string body = JsonSerializer.Serialize(new { trigger_id = "12345.98765.abcd2358fdea", view });
With a Slack client library
When your application already uses a .NET Slack client such as SlackNet, pass slackblocks JSON through that library's raw request support, and keep its delivery, retries, and rate-limit handling. Alias one namespace if a file imports both libraries, because several type names overlap.
With a Slack Web API client
slackblocks constructs payloads and leaves delivery to your chosen HTTP or Slack client. The official slack-ruby-client gem is an optional application dependency.
require "slackblocks"
require "slack-ruby-client"
message = Slackblocks::MessagePayload.new(
channel: "C01234567",
text: "Deployment complete",
blocks: [Slackblocks::SectionBlock.new(text: "Deployment complete")]
)
client = Slack::Web::Client.new(token: ENV.fetch("SLACK_BOT_TOKEN"))
client.chat_postMessage(**message.as_json.transform_keys(&:to_sym))
as_json returns a fresh hash, so a client may modify it without changing the message. For an incoming webhook, construct Slackblocks::WebhookMessage and send its to_json as an application/json request body. Use MessageResponse for interaction responses, ModalView for modals, and HomeTabView for App Home.