feat(types): add ContainerBlock for block kit #2721

Open
srtaalej opened 10:31am on August 31, 2026 wants to merge 65 Ξ” into slackapi/node-slack-sdk main from
ale-add-containerblock
srtaalej authored
of work between August 31 and September 1
Diff Delta:
65
About 20 Diff Delta/hour
Classified as:  Feature Add, General

srtaalej's Description of Work

Summary

Adds ContainerBlock (type: "container") to @slack/types per https://docs.slack.dev/reference/block-kit/blocks/container-block/


  • New ContainerBlock interface with all documented properties: title, rich_text_title, subtitle, child_blocks, width, icon, is_collapsible, default_collapsed, has_header_divider

  • New ContainerBlockChildBlock helper union type for the 11 supported child block types

  • Added to the KnownBlock discriminated union

  • Type tests covering happy path, sad path, and KnownBlock assignability

  • Note: the API rejects has_header_divider: true when is_collapsible: true β€” documented in JSDoc

Example Bolt test app (app.js)

Register a `/container-block` slash command in your test app and use the text argument to test different property combinations:

- `/container-block` β€” base (title + child_blocks)
- `/container-block width` β€” adds `width: "wide"`
- `/container-block mrkdwn-subtitle` β€” subtitle with `mrkdwn` type
- `/container-block has-header-divider` β€” adds `has_header_divider: true`
- `/container-block default-collapsed` β€” adds `is_collapsible` + `default_collapsed`
- `/container-block icon` β€” adds `icon` image element
- `/container-block rich-text-title` β€” uses `rich_text_title` instead of `title`
- `/container-block all-collapsible` β€” all props with collapsible
- `/container-block all-divider` β€” all props with header divider
- `/container-block all` β€” all compatible properties together

```javascript
import { App, LogLevel } from '@slack/bolt';
import { config } from 'dotenv';

config();

const app = new App({
token: process.env.SLACK_BOT_TOKEN,
socketMode: true,
appToken: process.env.SLACK_APP_TOKEN,
logLevel: LogLevel.DEBUG,
});

app.command('/container-block', async ({ ack, respond, command }) => {
await ack();
const variant = (command.text || '').trim();

const base = {
type: 'container',
title: { type: 'plain_text', text: 'Container Block Test' },
child_blocks: [
{ type: 'section', text: { type: 'mrkdwn', text: 'Section inside a container.' } },
{ type: 'divider' },
{ type: 'section', text: { type: 'mrkdwn', text: 'Another section below the divider.' } },
],
};

if (variant === 'width') {
base.width = 'wide';
} else if (variant === 'mrkdwn-subtitle') {
base.subtitle = { type: 'mrkdwn', text: '*Bold* subtitle' };
} else if (variant === 'has-header-divider') {
base.has_header_divider = true;
} else if (variant === 'default-collapsed') {
base.is_collapsible = true;
base.default_collapsed = true;
} else if (variant === 'icon') {
base.icon = {
type: 'image',
image_url: 'https://api.slack.com/img/blocks/bkb_template_images/plants.png',
alt_text: 'icon',
};
} else if (variant === 'rich-text-title') {
delete base.title;
base.rich_text_title = {
type: 'rich_text',
elements: [
{ type: 'rich_text_section', elements: [{ type: 'text', text: 'Rich Title' }] },
],
};
} else if (variant === 'all-collapsible') {
base.subtitle = { type: 'plain_text', text: 'Collapsible variant' };
base.width = 'wide';
base.icon = {
type: 'image',
image_url: 'https://api.slack.com/img/blocks/bkb_template_images/plants.png',
alt_text: 'icon',
};
base.is_collapsible = true;
} else if (variant === 'all-divider') {
base.subtitle = { type: 'plain_text', text: 'Header divider variant' };
base.width = 'wide';
base.icon = {
type: 'image',
image_url: 'https://api.slack.com/img/blocks/bkb_template_images/plants.png',
alt_text: 'icon',
};
base.has_header_divider = true;
} else if (variant === 'all') {
base.subtitle = { type: 'plain_text', text: 'All compatible properties' };
base.width = 'wide';
base.icon = {
type: 'image',
image_url: 'https://api.slack.com/img/blocks/bkb_template_images/plants.png',
alt_text: 'icon',
};
base.is_collapsible = true;
} else {
base.subtitle = {
type: 'plain_text',
text: 'Base test (try: width, mrkdwn-subtitle, has-header-divider, default-collapsed, icon, rich-text-title, all)',
};
}

await respond({ blocks: [base] });
});

(async () => {
await app.start();
console.log('⚑️ Bolt app is running!');
})();
```

Requirements

Building updated commit group diff...
3 total changed files
Loading changes...
Loading changes...