Skip to content

feat: add ContainerBlock layout block support - #1639

Open
srtaalej wants to merge 1 commit into
mainfrom
ale-add-containerblock
Open

feat: add ContainerBlock layout block support#1639
srtaalej wants to merge 1 commit into
mainfrom
ale-add-containerblock

Conversation

@srtaalej

@srtaalej srtaalej commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Add support for the Container Block layout block type.

Changes

  • ContainerBlock.java — New layout block with all documented properties: title, rich_text_title, subtitle, child_blocks, width, icon, is_collapsible, default_collapsed, has_header_divider, block_id
  • GsonLayoutBlockFactory — Registered container type for deserialization
  • Blocks.java — Added container() static helper
  • Kotlin DSL — Added ContainerBlockBuilder, LayoutBlockDsl.container(), and MultiLayoutBlockContainer.container()
  • Tests — Deserialization tests (full properties, rich_text_title, has_header_divider), builder round-trip test, static helper test
Example test app (App.java)
package blockkit;

import com.slack.api.bolt.AppConfig;
import com.slack.api.bolt.socket_mode.SocketModeApp;
import com.slack.api.model.block.RichTextBlock;
import com.slack.api.model.block.composition.MarkdownTextObject;
import com.slack.api.model.block.element.ButtonElement;
import com.slack.api.model.block.element.ImageElement;
import com.slack.api.model.block.element.RichTextInputElement;
import com.slack.api.model.block.element.RichTextSectionElement;


import java.util.Arrays;

import static com.slack.api.model.block.Blocks.*;
import static com.slack.api.model.block.composition.BlockCompositions.plainText;
import static com.slack.api.model.view.Views.*;

public class App {

    public static void main(String[] args) throws Exception {
        var app = new com.slack.api.bolt.App(AppConfig.builder()
                .singleTeamBotToken(System.getenv("SLACK_BOT_TOKEN"))
                .build());
                
        // /container-block — sends container blocks with variant subcommands
        app.command("/container-block", (req, ctx) -> {
            String variant = req.getPayload().getText() != null ? req.getPayload().getText().trim() : "";

            var childBlocks = asBlocks(
                    section(s -> s.text(MarkdownTextObject.builder().text("Section inside a container.").build())),
                    divider(),
                    section(s -> s.text(MarkdownTextObject.builder().text("Another section below the divider.").build()))
            );

            var builder = com.slack.api.model.block.ContainerBlock.builder()
                    .title(plainText("Container Block Test"))
                    .childBlocks(childBlocks);

            switch (variant) {
                case "width":
                    builder.width("wide");
                    break;
                case "mrkdwn-subtitle":
                    builder.subtitle(MarkdownTextObject.builder().text("*Bold* subtitle").build());
                    break;
                case "has-header-divider":
                    builder.hasHeaderDivider(true);
                    break;
                case "default-collapsed":
                    builder.isCollapsible(true);
                    builder.defaultCollapsed(true);
                    break;
                case "icon":
                    builder.icon(ImageElement.builder()
                            .imageUrl("https://api.slack.com/img/blocks/bkb_template_images/plants.png")
                            .altText("icon")
                            .build());
                    break;
                case "rich-text-title":
                    builder.title(null);
                    builder.richTextTitle(RichTextBlock.builder()
                            .elements(Arrays.asList(
                                    RichTextSectionElement.builder()
                                            .elements(Arrays.asList(
                                                    RichTextSectionElement.Text.builder().text("Rich Title").build()
                                            ))
                                            .build()
                            ))
                            .build());
                    break;
                case "all-collapsible":
                    builder.subtitle(plainText("Collapsible variant"));
                    builder.width("wide");
                    builder.icon(ImageElement.builder()
                            .imageUrl("https://api.slack.com/img/blocks/bkb_template_images/plants.png")
                            .altText("icon")
                            .build());
                    builder.isCollapsible(true);
                    break;
                case "all-divider":
                    builder.subtitle(plainText("Header divider variant"));
                    builder.width("wide");
                    builder.icon(ImageElement.builder()
                            .imageUrl("https://api.slack.com/img/blocks/bkb_template_images/plants.png")
                            .altText("icon")
                            .build());
                    builder.hasHeaderDivider(true);
                    break;
                case "all":
                    builder.subtitle(plainText("All compatible properties"));
                    builder.width("wide");
                    builder.icon(ImageElement.builder()
                            .imageUrl("https://api.slack.com/img/blocks/bkb_template_images/plants.png")
                            .altText("icon")
                            .build());
                    builder.isCollapsible(true);
                    break;
                default:
                    builder.subtitle(plainText("Base test (try: width, mrkdwn-subtitle, has-header-divider, default-collapsed, icon, rich-text-title, all)"));
                    break;
            }

            var resp = ctx.client().chatPostMessage(r -> r
                    .channel(req.getPayload().getChannelId())
                    .text("Container block test")
                    .blocks(asBlocks(builder.build())));
            System.out.println("chatPostMessage response - ok: " + resp.isOk() + ", error: " + resp.getError());
            if (resp.getError() != null) {
                System.out.println("Response metadata: " + resp.getResponseMetadata());
            }
            return ctx.ack();
        });


        String appToken = System.getenv("SLACK_APP_TOKEN");
        SocketModeApp socketModeApp = new SocketModeApp(appToken, app);
        socketModeApp.start();
    }
}

To run:

# First, install the SDK snapshot locally (from the java-slack-sdk repo):
./scripts/install_local.sh

# Then, from the bolt-java-block-kit-test directory:
SLACK_BOT_TOKEN=xoxb-... SLACK_APP_TOKEN=xapp-... mvn exec:java

Subcommands: /container-block, /container-block width, /container-block mrkdwn-subtitle, /container-block has-header-divider, /container-block default-collapsed, /container-block icon, /container-block rich-text-title, /container-block all-collapsible, /container-block all-divider, /container-block all

Note: has_header_divider and is_collapsible are mutually exclusive — the Slack API returns invalid_blocks if both are true.

Category (place an x in each of the [ ])

  • bolt (Bolt for Java)
  • bolt-{sub modules} (Bolt for Java - optional modules)
  • slack-api-client (Slack API Clients)
  • slack-api-model (Slack API Data Models)
  • slack-api-*-kotlin-extension (Kotlin Extensions for Slack API Clients)
  • slack-app-backend (The primitive layer of Bolt for Java)

Requirements

Please read the Contributing guidelines and Code of Conduct before creating this issue or pull request. By submitting, you agree to those rules.

@codecov

codecov Bot commented Aug 31, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 6.45161% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.69%. Comparing base (e30ba9d) to head (9406c8b).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...el/kotlin_extension/block/ContainerBlockBuilder.kt 0.00% 28 Missing ⚠️
...nsion/block/container/MultiLayoutBlockContainer.kt 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #1639      +/-   ##
============================================
- Coverage     72.84%   72.69%   -0.15%     
- Complexity     4533     4535       +2     
============================================
  Files           482      483       +1     
  Lines         14423    14454      +31     
  Branches       1512     1513       +1     
============================================
+ Hits          10506    10508       +2     
- Misses         3022     3051      +29     
  Partials        895      895              
Flag Coverage Δ
jdk-14 72.69% <6.45%> (-0.15%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@srtaalej srtaalej self-assigned this Aug 31, 2026
@srtaalej srtaalej added enhancement M-T: A feature request for new functionality project:slack-api-model project:slack-api-model semver:minor labels Aug 31, 2026
@srtaalej
srtaalej marked this pull request as ready for review August 31, 2026 20:08
@srtaalej
srtaalej requested a review from a team as a code owner August 31, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement M-T: A feature request for new functionality project:slack-api-model project:slack-api-model semver:minor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant