Skip to main content

Migrating from 1.x to 2.x

slackblocks 2.0 is a major version bump that focuses on cleaning up internal correctness issues, modernising the type-annotation surface, and adding several ergonomic helpers. The good news: for almost all users, upgrading is a no-op beyond changing the Python version pin.

This page covers what actually changes for you and what's worth adopting once you've upgraded.

Quick checklist​

StepAction
1Confirm you are on Python 3.10 or newer. If not, stay on slackblocks ~= 1.2.
2Bump your dependency pin to slackblocks ~= 2.0 (or >=2,<3).
3Run your test suite. Existing code should continue to work unchanged.
4Optionally adopt the new conveniences below.

Truly breaking changes​

There is exactly one breaking change you might trip over:

Python 3.8 and 3.9 are no longer supported​

Both reached upstream end of life (3.8 EOL Oct 2024, 3.9 EOL Oct 2025) and are not receiving security patches.

If you are still on either:

  • Stay on the 1.x line until you can upgrade Python. The 1.x line is maintained on a best-effort basis for bugfixes; see the Compatibility page.
  • Pin via your package manager:
slackblocks>=1,<2

That's it for breaking changes. Nothing else in the public API was removed or renamed.

What's not breaking, but worth adopting​

PlainText and Markdown instead of Text(type_=...)​

Before:

from slackblocks import Text, TextType

heading = Text("Welcome", type_=TextType.PLAINTEXT, emoji=True)
body = Text("**bold**", type_=TextType.MARKDOWN, verbatim=True)

After:

from slackblocks import PlainText, Markdown

heading = PlainText("Welcome", emoji=True)
body = Markdown("**bold**", verbatim=True)

PlainText and Markdown are subclasses of Text, so anywhere a Text or TextLike was accepted before still accepts the new types. The rendered JSON is byte-identical to the equivalent Text calls.

Workflow.from_url(...) instead of nested Workflow(trigger=Trigger(...))​

Before:

from slackblocks import Workflow, Trigger, InputParameter

workflow = Workflow(
trigger=Trigger(
url="https://slack.com/shortcuts/...",
customizable_input_parameters=[
InputParameter(name="env", value="prod"),
InputParameter(name="retries", value="3"),
],
),
)

After:

from slackblocks import Workflow

workflow = Workflow.from_url(
"https://slack.com/shortcuts/...",
env="prod",
retries="3",
)

The verbose form continues to work, so you do not have to migrate.

block_kit_builder_url(...) for browser preview​

New helper that turns any block, list of blocks, message, view, or raw dict into a Block Kit Builder URL. Open the URL in your browser to see exactly what Slack will render before you ship.

from slackblocks import SectionBlock, block_kit_builder_url

print(block_kit_builder_url(SectionBlock("Hi")))
# https://app.slack.com/block-kit-builder/#%7B%22blocks%22%3A%5B...%5D%7D

Pass team_id="T0123ABCD" for a workspace-specific URL.

Typed exceptions for finer-grained error handling​

InvalidUsageError now has five subclasses, raised in well-defined situations:

SubclassRaised when
LengthErrorA string or list violates a min/max-length constraint.
RangeErrorA numeric value violates a min/max-value constraint.
TypeMismatchErrorWrong type or unexpected discrete value.
MutualExclusivityErrorTwo args that must not both be set were both set.
MissingRequiredErrorAt least one of a set of args was required.
from slackblocks import Button, LengthError

try:
Button(text="x" * 100, action_id="b")
except LengthError as e:
# specifically a length violation
handle_length(e)

Existing except InvalidUsageError blocks continue to catch every subclass — no change required to upgrade. The new subclasses are purely additive.

Round-tripping incoming Slack JSON with from_dict​

If your app receives Slack interactivity payloads or events and wants to inspect the blocks, you can now parse them back into slackblocks objects:

import json
from slackblocks import Block

incoming = json.loads(slack_request_body)
block = Block.from_dict(incoming["blocks"][0])
print(block.text.text) # for a SectionBlock, etc.

Block.from_dict reads data["type"] and dispatches to the right subclass. Composition objects (Text, Option, Confirm, etc.) and the simple blocks (DividerBlock, FileBlock, HeaderBlock, MarkdownBlock, ImageBlock, SectionBlock, ContextBlock, VideoBlock) round-trip fully.

Blocks that contain interactive elements (ActionsBlock, InputBlock, SectionBlock with an accessory, ContextBlock with image elements) raise NotImplementedError for now; these depend on Element.from_dict which is planned for a follow-up release.

Two new block types​

Slack added these in 2024; they're now available:

from slackblocks import MarkdownBlock, VideoBlock

# Slack's GitHub-flavored markdown block (richer than SectionBlock mrkdwn).
MarkdownBlock(text="# Heading\n\n- item 1\n- item 2")

# Embed a video from a Slack-supported provider.
VideoBlock(
alt_text="Demo",
thumbnail_url="https://example.com/thumb.png",
title="Getting Started",
video_url="https://example.com/video.mp4",
)

Type-checking improvements​

The 2.x series ships with substantially tighter type signatures. If you type-check your code with mypy, pyright, or Pyre, expect that:

  • Text.to_text("hi") is now correctly typed as Text (was previously Text | None). Code that did assert text is not None after to_text calls can drop the assertion.
  • Button(style="warning") is now flagged at type-check time — style is Literal["primary", "danger"] | ButtonStyle | None.
  • ColumnSettings(align="LEFT") is similarly flagged at type-check time.
  • ConversationFilter(include=["bogus"]) is flagged.

These constraints already existed at runtime; the difference is that mypy now agrees with the runtime behaviour.

Internal changes you might notice​

The _resolve() method on every block, element, and composition object was refactored to use a shared slackblocks._core.resolve() walker. This is strictly internal — JSON output is byte-identical to 1.x — but if you have been subclassing internal classes you may notice the _resolve bodies are much shorter.

If you are extending slackblocks with custom classes:

  • If your custom class has a _resolve method that follows the existing pattern (manual if self.x is not None: out["x"] = self.x._resolve()), it continues to work in 2.x without modification.
  • If you want, you can switch to using the new slackblocks._core.resolve / omit_none helpers; doing so guarantees recursion into nested _resolve outputs and is much more concise.

Reporting issues​

If you hit a real upgrade problem, please open an issue on GitHub — include your Python version, your slackblocks version (old and new), and a minimal repro.