Skip to main content
Python v3.0.0

Migration

Upgrading to 3.0.0​

All nine implementations move to 3.0.0 together. Minimum supported language and runtime versions are unchanged; see Compatibility.

For applications that construct ordinary messages and buttons with valid inputs, the main upgrade is the dependency version, plus the import-path change for Go. Review the specific cases below if you use number-input bounds, explicit response visibility or inputs that the earlier validation accepted. The new Slackbot button prompt fields are optional and require no changes to existing buttons.

  • Number-input bounds now serialize and read back as strings. Numeric constructor and builder conveniences remain, but callers using numeric getters, C# constructor reflection, Elixir direct struct edits or numeric JSON bounds must migrate. See number-input bounds below.
  • Go imports move from github.com/nicklambourne/slackblocks/go/v2 to github.com/nicklambourne/slackblocks/go/v3. Run go get github.com/nicklambourne/slackblocks/go/v3@v3.0.0, update imports, then run go mod tidy and your tests. Go 1.22 and the default slack-go v0.17.3 dependency remain supported; choosing the separately tested v0.23.1 SDK still requires Go 1.25.
  • Validation now rejects previously accepted invalid inputs, including explicit external-multiselect focus, inverted input bounds, malformed date/time shapes and more than two message charts (including attachments). Review the language changelog and test your final payloads.
  • Python callers passing ephemeral=False now explicitly request in_channel; omit the option to retain the wire default.

The frozen 2.8.0 docs remain available in the version selector. The older migration notes below describe their named releases.

Upgrading Python 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 dispatches by type; direct typed parsers also require the matching discriminator. This is a partial parser, not a general Slack JSON loader:

Parse supportBlock types
SupportedAlert, Divider, File, Header, Image, Markdown, Video
Supported subsetSection without accessory; Context with text elements only
Raises NotImplementedErrorActions, Input, RichText, Table, Card, Carousel, Container, ContextActions, DataTable, DataVisualization, TaskCard, Plan; Section with accessory; Context with images

Unknown future fields are ignored, so this is not a lossless storage format. Keep the original JSON when forwarding or archiving unsupported or future fields. Text, ConfirmationDialogue, Option, OptionGroup, DispatchActionConfiguration, ConversationFilter, InputParameter, SlackFile, Trigger, and Workflow have composition-object parsers; check individual references rather than assuming every composition type is parseable.

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.

Number-input bounds in spec 1.3.0​

Slack's number-input reference requires min_value and max_value to be JSON strings. Earlier packages emitted numbers, which ordinary Slack modal writes rejected. The native APIs now emit strings and compare the bounds numerically, rejecting inverted ranges, invalid strings, and nonfinite numeric values. Omitted bounds remain omitted.

Numeric construction remains available: Python, TypeScript, Go, Ruby, PHP and Elixir accept a numeric convenience argument or a string; Java has string and numeric builder overloads; Rust setters accept a displayable numeric value or string. C# uses the public NumberInputBound scalar with implicit string, integer, double, nullable-double and decimal conversions. Existing ordinary numeric constructor calls still work. C# callers using reflection must account for the new parameter type. This scalar belongs to the elements.number_input capability and is exercised by its constructor and conformance tests.

Getters and serialized values now expose strings in every language. In particular, Java's getMinValue/getMaxValue now return Optional<String>, C# properties return string?, and Rust getters return Option<&str>. Elixir struct fields contain binary() | nil; keyword constructors and change/2 retain numeric conveniences, while parsed maps and direct struct edits must use strings. This is a deliberate source/wire contract correction: callers that inspect numeric getters or JSON numbers must migrate. Pass strings when decimal spelling or integer precision matters. This does not normalize initial_value or establish a trailing-zero prohibition; that separate Slack runtime discrepancy remains documented in troubleshooting. These API changes ship in coordinated release 3.0.0.

Bounds use signed ASCII decimal/scientific strings (for example -1.25, .5, 2. or 1e-3). Whitespace, underscores, hex and nonfinite spellings are rejected. Comparisons preserve precision beyond floating point, including long fractions and integers above 2^53. Equivalent strings such as 0.1000 and 1e-1 remain unchanged on the wire. The library accepts mathematically finite strings such as 1e309; this does not establish Slack delivery support for every magnitude.

Recorded views.open probes on 2026-10-13 rejected bound strings 1e2, 1e309, +10, .5, 0x10, 1_0, 10 and 1., although blocks.validate accepted them. For ordinary delivery, use conventional decimal strings such as "0", "12.5" and "100"; the preserved scientific/alternate forms remain library inputs rather than a Slack delivery guarantee. Native numeric convenience conversions may also produce scientific notation for very large or small values. Applications targeting those magnitudes should test their final native payload against the receiving Slack API.

External multi-select focus​

Slack's external-data multi-select field table does not support focus_on_load. Omit the parameter, including an explicit false, when constructing an external multi-select. The compatibility parameter remains callable but now raises invalid-usage; it is not silently ignored. This restriction does not apply to the other selector types whose Slack field tables include focus. Python's default changes from false to None so omission can be distinguished from an explicitly supplied false value.