slack-morphism 2.28.0

Slack Morphism is a modern client library for Slack Web/Events API/Socket Mode and Block Kit
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
# Block Kit support

Slack Block Kit messages and views are JSON documents built from a fixed set
of block and element types. This library models each of those types as a
Rust struct or enum, and provides a small set of macros — `slack_blocks!`,
`md!`, `pt!` — to build the `Vec` of blocks or elements each block-holding
type expects, without writing `.into()` on every item by hand.

Everything below is real, compiling code. Run it yourself from
`examples/blocks_showcase.rs`, or copy any snippet directly.

## From JSON to Rust

Take a very simple block, as Slack's Block Kit Builder shows it:

```json
{
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "A message *with some bold text* and _some italicized text_."
      }
    }
  ]
}
```

The same block in type-safe Rust is one struct per JSON object, with the
`type` tag implied by the struct, and `md!` standing in for the `mrkdwn`
text object:

```rust,noplaypen
use slack_morphism::prelude::*;

let blocks: Vec<SlackBlock> = slack_blocks![
    SlackSectionBlock::new()
        .with_text(md!("A message *with some bold text* and _some italicized text_.")),
];
```

Every block, element and composition object follows this pattern: a JSON
object becomes `SlackXxx::new(required fields)`, each optional key becomes a
`.with_key(..)` call, and a JSON array of blocks or elements becomes a
`slack_blocks![..]` list.

## Quick start

The smallest possible message is a single section block with some markdown
text, wrapped in a `SlackMessageContent` and posted with
`SlackApiChatPostMessageRequest`:

```rust,no_run,noplaypen
use slack_morphism::prelude::*;

# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = SlackClient::new(SlackClientHyperConnector::new()?);
let token: SlackApiToken = SlackApiToken::new("xoxb-your-token".into());
let session = client.open_session(&token);

let blocks: Vec<SlackBlock> = slack_blocks![
    SlackSectionBlock::new().with_text(md!("A message *with some bold text*.")),
];

let content = SlackMessageContent::new().with_blocks(blocks);
let request = SlackApiChatPostMessageRequest::new("#general".into(), content);

session.chat_post_message(&request).await?;
# Ok(())
# }
```

The rest of this page builds up from here: how text objects work, how
`slack_blocks!` builds lists, and how each block and element family is
constructed.

## Text objects

Block Kit has two flavors of text: `mrkdwn` (Slack's own markdown) and
`plain_text`. The library models them as `SlackBlockMarkDownText` and
`SlackBlockPlainText`, and the `md!` and `pt!` macros build them:

```rust,noplaypen
use slack_morphism::prelude::*;

let markdown: SlackBlockMarkDownText = md!("A message *with some bold text*.");
let plain: SlackBlockPlainText = pt!("Plain text");

// Both macros format like `format!` when given more than one argument.
let user_id = SlackUserId("U1234".into());
let greeting: SlackBlockMarkDownText = md!("Hey {}, welcome!", user_id.to_slack_format());
```

`md!` and `pt!` convert into whatever type the call site expects via
`Into`, so the same macro call can produce a `SlackBlockText`, a
`SlackContextBlockElement`, or the raw `SlackBlockMarkDownText`/
`SlackBlockPlainText`, depending on where it is used.

Some fields require plain text specifically and reject markdown at the API
level: block headers, button labels, and input labels are typed as
`SlackBlockPlainTextOnly`, not `SlackBlockText`. Passing markdown there does
not fail to compile — a plain string still converts — but Slack rejects the
request at runtime with `invalid_blocks` if the string contains markdown
syntax Slack cannot render as plain text. Use `pt!`, or a bare string
literal, for these fields.

## Building block lists

`slack_blocks!` builds a `Vec<T>` from a comma-separated list, converting
each item with `.into()`. Any block or element type that has a `From` (or
`Into`) conversion to the target item type can appear as a bare item:

```rust,noplaypen
use slack_morphism::prelude::*;

let show_divider = true;

let blocks: Vec<SlackBlock> = slack_blocks![
    SlackHeaderBlock::new(pt!("Weekly report")),
    optionally(show_divider => SlackDividerBlock::new()),
    ..(1..=3).map(|n| SlackSectionBlock::new().with_text(md!("Item {}", n))),
];

assert_eq!(blocks.len(), 5);
```

- A bare expression is pushed via `.into()`.
- `optionally(pred => item)` converts and pushes `item` the same way a bare
  item does, only when `pred` is true; neither `pred` nor `item` is
  evaluated when it is false.
- `..iter` splices every element of an iterator or `Vec` in, each converted
  via `.into()` — useful for building a run of blocks from data instead of
  writing them out by hand, or for spreading in blocks assembled elsewhere,
  as in [Templates]#templates below.

These forms mix freely in one list. An older `some(item)` / `some_into(item)`
/ `optionally_into(pred => item)` syntax still works and appears throughout
older code and examples; prefer the bare form above in new code.

One limit worth knowing up front: a bare `md!(..)` or `pt!(..)` call cannot
be a list item directly, because its expansion already ends in an untyped
`.into()` and the compiler has nothing left to infer the item type from
(`E0283`). This only matters for a list whose items are text objects
themselves, such as a context block's elements or a section's fields — give
the list an explicit element type instead, as in
`SlackContextBlock::new(vec![md!("hi")])` or
`.with_fields(vec![md!("hi")])`, both used below.

## Sections

A section block carries a text object, optional fields (short text/value
pairs laid out in a grid), and an optional accessory element:

```rust,noplaypen
use slack_morphism::prelude::*;

let section = SlackSectionBlock::new()
    .with_text(md!("*Deploy status*: all systems green"))
    .with_fields(vec![md!("*Region:*\nus-east-1"), md!("*Duration:*\n42s")])
    .with_accessory(
        SlackBlockButtonElement::new("view-details".into(), pt!("Details")).into(),
    );

let block: SlackBlock = section.into();
```

## Actions and inputs

Action blocks hold interactive elements — buttons, selects, pickers. Input
blocks pair one element with a required label, for use in modals.

```rust,noplaypen
use slack_morphism::prelude::*;

let approve = SlackBlockButtonElement::new("approve".into(), pt!("Approve"))
    .with_style(SlackBlockButtonStyle::Primary);
let deny = SlackBlockButtonElement::new("deny".into(), pt!("Deny"))
    .with_style(SlackBlockButtonStyle::Danger);

let actions: SlackBlock = SlackActionsBlock::new(slack_blocks![approve, deny]).into();

let region_select = SlackBlockStaticSelectElement::new("region".into())
    .with_placeholder(pt!("Choose a region"))
    .with_options(vec![
        SlackBlockChoiceItem::new("US East".into(), "us-east-1".into()),
        SlackBlockChoiceItem::new("EU West".into(), "eu-west-1".into()),
    ]);

let date_picker = SlackBlockDatePickerElement::new("deploy-date".into())
    .with_placeholder(pt!("Pick a date"));

let date_input: SlackBlock =
    SlackInputBlock::new("Deploy date".into(), date_picker.into()).into();

let _ = (actions, region_select, date_input);
```

## Context

Context blocks hold a small run of text and image elements, usually shown in
a muted style below other content. As noted above, its element list needs an
explicit element type, so bare `md!`/`pt!` calls work directly inside
`vec![...]` without going through `slack_blocks!`:

```rust,noplaypen
use slack_morphism::prelude::*;

let context: SlackBlock =
    SlackContextBlock::new(vec![md!("Posted by "), pt!("the release bot")]).into();
```

## Rich text

Rich text blocks are Slack's structured formatting model: sections made of
inline runs (text, links, users, emoji, ...), lists, quotes, and
preformatted code. A bare string converts into an unstyled text run
wherever an inline element is expected, and `SlackRichTextText` has
`.bold()`, `.italic()`, `.strike()`, and `.code()` helpers for styled runs:

```rust,noplaypen
use slack_morphism::prelude::*;

let section: SlackRichTextElement = SlackRichTextSection::new(vec![
    "Build ".into(),
    SlackRichTextText::new("passed".to_string()).bold().into(),
    " ".into(),
    SlackRichTextEmoji::new(SlackEmojiName("white_check_mark".into())).into(),
])
.into();

let list: SlackRichTextElement = SlackRichTextList::new(
    SlackRichTextListStyle::Bullet,
    vec!["Unit tests".into(), "Integration tests".into()],
)
.into();

let quote: SlackRichTextElement =
    SlackRichTextQuote::new(vec!["Ship it.".into()]).into();

let preformatted: SlackRichTextElement =
    SlackRichTextPreformatted::new(vec!["cargo test --doc".into()]).into();

let rich_text: SlackBlock =
    SlackRichTextBlock::new(vec![section, list, quote, preformatted]).into();
```

The same bare-string conversion applies inside a `SlackRichTextList`'s items
and a rich text table cell, below.

## Tables

Table blocks are a `Vec<Vec<SlackTableCell>>`. A bare string becomes a
raw-text cell; a `SlackTableRichTextCell` holds rich text elements the same
way a rich text block does:

```rust,noplaypen
use slack_morphism::prelude::*;

let table: SlackBlock = SlackTableBlock::new(vec![
    vec!["Service".into(), "Status".into()],
    vec![
        "api".into(),
        SlackTableRichTextCell::new(vec![SlackRichTextSection::new(vec![
            SlackRichTextText::new("healthy".to_string()).bold().into(),
        ])
        .into()])
        .into(),
    ],
])
.into();
```

## Templates

`SlackMessageTemplate` renders a full `SlackMessageContent`;
`SlackBlocksTemplate` renders just a `Vec<SlackBlock>` meant to be spread
into a larger message with `..`. A typical template takes its parameters as
a struct built with `rsb_derive::Builder`:

```rust,noplaypen
use slack_morphism::prelude::*;
use rsb_derive::Builder;

#[derive(Debug, Clone, Builder)]
struct DeploySummaryParams {
    service: String,
    ok: bool,
}

impl SlackBlocksTemplate for DeploySummaryParams {
    fn render_template(&self) -> Vec<SlackBlock> {
        slack_blocks![
            SlackDividerBlock::new(),
            SlackContextBlock::new(vec![md!("Deployed by the release bot")]),
        ]
    }
}

#[derive(Debug, Clone, Builder)]
struct WelcomeMessageParams {
    user_id: SlackUserId,
}

impl SlackMessageTemplate for WelcomeMessageParams {
    fn render_template(&self) -> SlackMessageContent {
        let footer = DeploySummaryParams::new("api".into(), true);

        SlackMessageContent::new()
            .with_text(format!("Hey {}", self.user_id.to_slack_format()))
            .with_blocks(slack_blocks![
                SlackSectionBlock::new()
                    .with_text(md!("Hey {}, welcome!", self.user_id.to_slack_format())),
                ..footer.render_template(),
            ])
    }
}

let message = WelcomeMessageParams::new("U1234".into());
let content = message.render_template();
assert_eq!(content.blocks.map(|b| b.len()), Some(3));
```

## Views and modals

A modal is a `SlackModalView`: a title, a list of blocks (typically inputs),
and optional submit/close button text. Opening one requires a `trigger_id`
from the interaction that triggered it — a button click or a shortcut
invocation, not something you can obtain outside of a live request. Publishing
a home tab view uses `views.publish` and a user id instead:

```rust,no_run,noplaypen
use slack_morphism::prelude::*;

# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let client = SlackClient::new(SlackClientHyperConnector::new()?);
let token: SlackApiToken = SlackApiToken::new("xoxb-your-token".into());
let session = client.open_session(&token);

let name_input = SlackBlockPlainTextInputElement::new("name".into());

let modal = SlackModalView::new(
    "Deploy".into(),
    vec![SlackInputBlock::new("Service name".into(), name_input.into()).into()],
)
.with_submit(pt!("Deploy"));

let open_request = SlackApiViewsOpenRequest::new(
    SlackTriggerId("123.456.abcdef".into()),
    SlackView::Modal(modal),
);
session.views_open(&open_request).await?;

let home = SlackHomeView::new(vec![SlackSectionBlock::new()
    .with_text(md!("Welcome to your home tab."))
    .into()]);

let publish_request =
    SlackApiViewsPublishRequest::new(SlackUserId("U1234".into()), SlackView::Home(home));
session.views_publish(&publish_request).await?;
# Ok(())
# }
```

## Troubleshooting `invalid_blocks`

Slack validates blocks server-side and returns `invalid_blocks` with little
detail beyond that. The usual causes:

- **Markdown where plain text is required.** Header text, button labels,
  input labels, and similar `SlackBlockPlainTextOnly` fields do not render
  `mrkdwn` syntax; passing text with `*bold*` or a link in one of them either
  shows the literal asterisks or is rejected outright, depending on the
  field.
- **Text over 3000 characters.** Section and context text objects have a
  3000-character limit; button and option text has a much smaller one (75
  characters for a button label).
- **More than 50 blocks in one message**, or more than 100 in a modal or
  home tab.
- **A missing or duplicate `action_id`.** Every interactive element needs an
  `action_id` unique within its view or message; the button, select, and
  input constructors above all take it as their first argument for this
  reason.

When none of the above explains it, the fastest way to isolate a bad block is
to paste the JSON from
[Block Kit Builder](https://app.slack.com/block-kit-builder) straight into
`serde_json::from_str::<Vec<SlackBlock>>`, confirm it deserializes and
matches what you expected, then compare it field by field against the blocks
you built:

```rust,noplaypen
use slack_morphism::prelude::*;

let payload = r#"[
    {
        "type": "section",
        "text": { "type": "mrkdwn", "text": "A message *with some bold text*." }
    }
]"#;

let blocks: Vec<SlackBlock> = serde_json::from_str(payload)?;
assert_eq!(blocks.len(), 1);
# Ok::<(), serde_json::Error>(())
```

## Convert Block Kit Builder JSON

Paste the JSON from
[Block Kit Builder](https://app.slack.com/block-kit-builder) below and press
Convert (or Ctrl+Enter) to get the equivalent builder code. The converter
runs entirely in your browser: nothing is uploaded anywhere. It accepts a
Builder message (`{"blocks": [...]}`), a bare block array, a single block, or
a modal or home view; anything the crate cannot model is reported as an
error rather than guessed at.

<div id="blockkit-to-rust" data-assets="blockkit/">The converter runs on the
<a href="https://slack-rust.abdolence.dev/block-kit-support.html">documentation
site</a>; open this page there to use it.</div>

The same conversion is available as a command-line tool:

```sh
cargo run --manifest-path tools/blockkit-to-rust/Cargo.toml --bin bk2rs < message.json
```

## More examples

`examples/blocks_showcase.rs` posts one message per block family covered on
this page and prints a modal view. `examples/client.rs` shows a complete,
runnable client setup including authentication. `examples/socket_mode.rs`
shows handling interactions (button clicks, view submissions) that come back
from the blocks you post.