NewYour coding agent can read the release notes before it upgrades.Set up the MCP server →
PyPI · #1071 most downloaded on PyPI
The Bolt Framework for Python
Last release 2 months ago
15 Jul 2026
Release timing varies
gaps range from 8 days to 5 months
Nearly every release is documented
notes for 60 of the last 60 stable releases
Nothing withdrawn
no release was ever pulled
6 years old
135 releases · first in 2020
One column per quarter.
feat: widen set_suggested_prompts initialization scope to any DM by @WilliamBergamin in #1549
Full Changelog: v1.29.0...v1.30.0
feat: add authorship arguments to say_stream by @zimeg in #1502
Full Changelog: v1.28.0...v1.29.0
Bring magic to a conversation with say_stream for streaming messages and show loading status with set_status . Now available for app.event and app.mes
Bring magic to a conversation with say_stream for streaming messages and show loading status with set_status. Now available for app.event and app.message listeners:
@app.event("app_mention")
def handle_mention(say_stream, set_status):
set_status(
status="Thinking...",
loading_messages=["Waking up...", "Loading a witty response..."],
)
stream = say_stream(buffer_size=100)
stream.append(markdown_text="Thinking... :thinking_face:\n\n")
stream.append(markdown_text="Here is my response!")
stream.stop()Full Changelog: v1.27.0...v1.28.0
Milestone: https://github.com/slackapi/bolt-python/milestone/96
Package: https://pypi.org/project/slack-bolt/1.28.0/
feat: add has_been_called on complete & fail utility by @WilliamBergamin in #1390
complete & fail utility by @WilliamBergamin in #1390Full Changelog: v1.26.0...v1.27.0
Milestone: https://github.com/slackapi/bolt-python/milestone/95?closed=1
AI-Enabled Features: Loading States, Text Streaming, and Feedback Buttons
Try the AI Agent Sample app to explore the AI-enabled features and existing Assistant helper:
# Create a new AI Agent app
$ slack create slack-ai-agent-app --template slack-samples/bolt-python-assistant-template
$ cd slack-ai-agent-app/
# Initialize Python Virtual Environment
$ python3 -m venv .venv
$ source .venv/bin/activate
$ pip install -r requirements.txt
# Add your OPENAI_API_KEY
$ export OPENAI_API_KEY=sk-proj-ahM...
# Run the local dev server
$ slack runAfter the app starts, send a message to the "slack-ai-agent-app" bot for a unique response.
Loading states allows you to not only set the status (e.g. "My app is typing...") but also sprinkle some personality by cycling through a collection of loading messages.
Bolt Assistant Class usage:
@assistant.user_message
def respond_in_assistant_thread(
client: WebClient,
context: BoltContext,
get_thread_context: GetThreadContext,
logger: Logger,
payload: dict,
say: Say,
set_status: SetStatus,
):
set_status(
status="thinking...",
loading_messages=[
"Teaching the hamsters to type faster…",
"Untangling the internet cables…",
"Consulting the office goldfish…",
"Polishing up the response just for you…",
"Convincing the AI to stop overthinking…",
],
)Web Client SDK usage:
@app.message()
def handle_message(client, context, event, message):
client.assistant_threads_setStatus(
channel_id=channel_id,
thread_ts=thread_ts,
status="thinking...",
loading_messages=[
"Teaching the hamsters to type faster…",
"Untangling the internet cables…",
"Consulting the office goldfish…",
"Polishing up the response just for you…",
"Convincing the AI to stop overthinking…",
],
)
# Start a new message streamThe chat_stream() helper utility can be used to streamline calling the 3 text streaming methods:
# Start a new message stream
streamer = client.chat_stream(
channel=channel_id,
recipient_team_id=team_id,
recipient_user_id=user_id,
thread_ts=thread_ts,
)
# Loop over OpenAI response stream
# https://platform.openai.com/docs/api-reference/responses/create
for event in returned_message:
if event.type == "response.output_text.delta":
streamer.append(markdown_text=f"{event.delta}")
else:
continue
feedback_block = create_feedback_block()
streamer.stop(blocks=feedback_block)Alternative to the Text Streaming Helper is to call the individual methods.
client.chat_startStreamFirst, start a chat text stream to stream a response to any message:
@app.message()
def handle_message(message, client, context, event, message):
# Start a new message stream
stream_response = client.chat_startStream(
channel=channel_id,
recipient_team_id=team_id,
recipient_user_id=user_id,
thread_ts=thread_ts,
)
stream_ts = stream_response["ts"]client.chat_appendStreamAfter starting a chat text stream, you can then append text to it in chunks (often from your favourite LLM SDK) to convey a streaming effect:
for event in returned_message:
if event.type == "response.output_text.delta":
client.chat_appendStream(
channel=channel_id,
ts=stream_ts,
markdown_text=f"{event.delta}"
)
else:
continueclient.chat_stopStreamLastly, you can stop the chat text stream to finalize your message:
client.chat_stopStream(
channel=channel_id,
ts=stream_ts,
blocks=feedback_block
)Add feedback buttons to the bottom of a message, after stopping a text stream, to gather user feedback:
def create_feedback_block() -> List[Block]:
blocks: List[Block] = [
ContextActionsBlock(
elements=[
FeedbackButtonsElement(
action_id="feedback",
positive_button=FeedbackButtonObject(
text="Good Response",
accessibility_label="Submit positive feedback on this response",
value="good-feedback",
),
negative_button=FeedbackButtonObject(
text="Bad Response",
accessibility_label="Submit negative feedback on this response",
value="bad-feedback",
),
)
]
)
]
return blocks
@app.message()
def handle_message(message, client):
# ... previous streaming code ...
# Stop the stream and add feedback buttons
feedback_block = create_feedback_block()
client.chat_stopStream(
channel=channel_id,
ts=stream_ts,
blocks=feedback_block
)chat_postMessage supports markdown_textresponse = client.chat_postMessage(
channel="C111",
markdown_text=markdown_content,
)Learn more in slackapi/python-slack-sdk#1718
📚 https://docs.slack.dev/reference/block-kit/blocks/markdown-block/
from slack_sdk.models.blocks import MarkdownBlock
...
@app.message("hello")
def message_hello(say):
say(
blocks=[
MarkdownBlock(text="**lets's go!**"),
],
text="let's go!",
)Learn more in slackapi/python-slack-sdk#1748
Add support for the workflows.featured.{add|list|remove|set} methods:
app.client.workflows_featured_add(channel_id="C0123456789", trigger_ids=["Ft0123456789"])
app.client.workflows_featured_list(channel_ids="C0123456789")
app.client.workflows_featured_remove(channel_id="C0123456789", trigger_ids=["Ft0123456789"])
app.client.workflows_featured_set(channel_id="C0123456789", trigger_ids=["Ft0123456789"])Learn more in slackapi/python-slack-sdk#1712
Milestone: https://github.com/slackapi/bolt-python/milestone/94?closed=1
Full Changelog: v1.25.0...v1.26.0
Package: https://pypi.org/project/slack-bolt/1.26.0/
Nothing published for this version
Nothing published for this version
chore(release): version 1.26.0.dev2
chore(release): version 1.26.0.dev2
chore(release): version 1.26.0.dev1
chore(release): version 1.26.0.dev1
Nothing published for this version
chore: remove 3.6 support by @WilliamBergamin in https://github.com/slackapi/bolt-python/pull/1359
feat: surface ack_timeout on the function handler by @WilliamBergamin in https://github.com/slackapi/bolt-python/pull/1351
ack_timeout on the function handler by @WilliamBergamin in https://github.com/slackapi/bolt-python/pull/1351ack_timeout on the function handler by @WilliamBergamin in #1351fix: honor passing bolt logger in default web client installation by @WilliamBergamin in https://github.com/slackapi/bolt-python/pull/1272
We've fixed a regression around the logger, by default the logger used by the WebClient will be the same as the one used by the Bolt application. If the WebClient defines its own logger then it will be used:
my_logger = logging.getLogger("my_logger")
app = App(client=WebClient(token=os.environ.get("SLACK_BOT_TOKEN"), logger=my_logger))
@app.command("/sample-command")
def sample_command(ack: Ack, client: WebClient):
ack()
assert client.logger.name == my_logger.name
docs: Custom steps for JIRA tutorial. by @technically-tracy in https://github.com/slackapi/bolt-python/pull/1214
Release Milestone: https://github.com/slackapi/bolt-python/milestone/89?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/88?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/87?closed=1
A better Agents & Assistants support in Bolt is now available!
A better Agents & Assistants support in Bolt is now available!
While you already can implement your agents using app.event(...) listeners for assistant_thread_started, assistant_thread_context_changed, and message events, Bolt offers a simpler approach. You just need to create an Assistant instance, attach the needed event handlers to it, and then add the assistant to your App instance.
assistant = Assistant()
# This listener is invoked when a human user opened an assistant thread
@assistant.thread_started
def start_assistant_thread(say: Say, set_suggested_prompts: SetSuggestedPrompts):
# Send the first reply to the human who started chat with your app's assistant bot
say(":wave: Hi, how can I help you today?")
# Setting suggested prompts is optional
set_suggested_prompts(
prompts=[
# If the suggested prompt is long, you can use {"title": "short one to display", "message": "full prompt"} instead
"What does SLACK stand for?",
"When Slack was released?",
],
)
# This listener is invoked when the human user sends a reply in the assistant thread
@assistant.user_message
def respond_in_assistant_thread(
payload: dict,
logger: logging.Logger,
context: BoltContext,
set_status: SetStatus,
say: Say,
client: WebClient,
):
try:
# Tell the human user the assistant bot acknowledges the request and is working on it
set_status("is typing...")
# Collect the conversation history with this user
replies_in_thread = client.conversations_replies(
channel=context.channel_id,
ts=context.thread_ts,
oldest=context.thread_ts,
limit=10,
)
messages_in_thread: List[Dict[str, str]] = []
for message in replies_in_thread["messages"]:
role = "user" if message.get("bot_id") is None else "assistant"
messages_in_thread.append({"role": role, "content": message["text"]})
# Pass the latest prompt and chat history to the LLM (call_llm is your own code)
returned_message = call_llm(messages_in_thread)
# Post the result in the assistant thread
say(text=returned_message)
except Exception as e:
logger.exception(f"Failed to respond to an inquiry: {e}")
# Don't forget sending a message telling the error
# Without this, the status 'is typing...' won't be cleared, therefore the end-user is unable to continue the chat
say(f":warning: Sorry, something went wrong during processing your request (error: {e})")
# Enable this assistant middleware in your Bolt app
app.use(assistant)
Please refer to https://tools.slack.dev/bolt-python/concepts/assistant/ and https://github.com/slack-samples/bolt-python-assistant-template for more details.
auto_acknowledge option for custom function handlers - Thanks @WilliamBergaminOptional typing of context.client - Thanks @WilliamBergaminbug: Fix #1131, async classes with "\_\_call\_\_" method are now accepted as middleware by @seratch in https://github.com/slackapi/bolt-python/pull/11
feat: add custom step support by @WilliamBergamin in https://github.com/slackapi/bolt-python/pull/1021
Release Milestone: https://github.com/slackapi/bolt-python/milestone/84?closed=1
https://github.com/slackapi/bolt-python/pull/1089 by @WilliamBergamin adds deprecation warnings to Steps from Apps components and documentation.
https://github.com/slackapi/bolt-python/pull/1085 by @WilliamBergamin introduces an WSGI adapter, this allows bolt to be deployed in production without the need of a 3rd party WSGI compatible web framework. check out the examples in examples/wsgi
https://github.com/slackapi/bolt-python/pull/1089 by @WilliamBergamin adds deprecation warnings to Steps from Apps components and documentation.
Full Changelog: https://github.com/slackapi/bolt-python/compare/v1.18.1...v1.19.0
Check https://github.com/slackapi/bolt-python/releases/tag/v1.19.0rc1 instead
Check https://github.com/slackapi/bolt-python/releases/tag/v1.19.0rc1 instead
Release Milestone: https://github.com/slackapi/bolt-python/milestone/79?closed=1
Test code improvements:
Release Milestone: https://github.com/slackapi/bolt-python/milestone/77?closed=1
Nothing published for this version
Release Milestone: https://github.com/slackapi/bolt-python/milestone/78?closed=1
Upgrade the slack-sdk package version to the latest - Thanks @seratch
In v1.17, two new optional properties bot_scopes and user_scopes have been added to the AuthorizeResult / AsyncAuthorizeResult classes. These properti
AuthorizeResult propertiesIn v1.17, two new optional properties bot_scopes and user_scopes have been added to the AuthorizeResult / AsyncAuthorizeResult classes. These properties are used to associate specific scopes with bot_token and user_token, and the built-in InstallationStore automatically resolves them.
bot_scopes: the scopes associated with the bot_token; this can be absent when bot_token does not existuser_scopes: the scopes associated with the user_token; this can be absent when user_token does not existThese properties are optional, so all the existing Authorize / AsyncAuthorize sub classes are expected to continue functioning without any code changes.
Also, this version includes the fix for the existing bug where the user_id can be absent when both bot_token and user_token exist.
Please refer to https://github.com/slackapi/bolt-python/pull/855 or the details of the changes.
contextStarting in v1.17, context objects in middleware and listeners provide a few new properties -- actor_enterprise_id, actor_team_id, and actor_user_id--, in addition to existing enterprise_id, team_id, and user_id. You should be curious about the difference. The new "actor" IDs remain the same for interactivity events such as slash commands, global shortcuts, etc. The key difference can appear when your app handles Events API subscription requests such as "app_mention" and "message" events in Slack Connect channels and/or when your app is distributed, and it has multiple workspace installations.
When your app is installed into multiple workspaces and/or by multiple users, the context.user_id can be any of the installed users' ones. Also, if your app is installed into multiple workspaces plus your app is added to a Slack Connect channel shared by those organizations, context.enterprise_id, context.team_id, and context.user_id are associated with any of the workspaces/organizations. Therefore, the tokens provided by bolt-python are still correct, as the tokens are associated with any installations for the received event.
However, when a user mentions your app's bot user in the Slack Connect channel, your app may desire to quickly check if the user (let us call this user "actor") has granted the app with the user's scopes. In this scenario, context.user_id etc. does not work. Instead, you must write your code to identify the "actor"'s workspace and user ID. The newly added "actor" IDs can easily help you handle such patterns. You can rely on the "actor" IDs as long as they exist. In other words, note that they can be absent for some events due to the lack of response data from the Slack server side. Such patterns can be improved by either SDK updates or server-side changes in future versions.
user_token_resolution optionRelated to the above, we added a new option called user_token_resolution: str for App / AsyncApp initialization. The available values for the option are "authed_user" and "actor". The default value is "authed_user", which is fully backward-compatible.
When you set "actor" for the option, your OAuth-enabled app's authorize function can behave differently. More specifically, the authorize function receives all the "actor" IDs. The built-in InstallationStore-based authorize tries to resolve the user token per request using "actor" IDs instead of context.user_id.
Setting "actor" for this option can be beneficial for the apps that require all the users to grant the app some use scopes. In this scenario, your app can easily identify the users who haven't installed the app with sufficient user scopes just by checking the existence of the user token and user scopes in the context.authorize_result object.
If your app does not request any user scopes when installing the app into a workspace, configuring this option does not have any effect on your app.
before_authorize optionTo skip unnecessary workload in a bolt-python app, now you can use before_authorize middleware function for it. Let's say your app receives "message" events but there is nothing to do with subtyped ones such as "message_changed" and "message_deleted". Your authorize function looks up installation data in your database and performs auth.test API calls. In this case, before_authorize can enable the app to skip the authorize operations for subtyped message events this way:
def skip_message_changed_events(payload: dict, next_):
if payload.get("type") == "message" and payload.get("subtype") in ["message_changed", "message_deleted"]:
# acknowledge the request and skip all the following middleware/listeners
return BoltResponse(status=200, body="")
next_()
Check https://github.com/slackapi/bolt-python/releases/tag/v1.17.0 instead
Check https://github.com/slackapi/bolt-python/releases/tag/v1.17.0 instead
Check https://github.com/slackapi/bolt-python/releases/tag/v1.17.0rc4 instead
Check https://github.com/slackapi/bolt-python/releases/tag/v1.17.0rc4 instead
Check https://github.com/slackapi/bolt-python/releases/tag/v1.17.0rc3 instead
Check https://github.com/slackapi/bolt-python/releases/tag/v1.17.0rc3 instead
Check https://github.com/slackapi/bolt-python/releases/tag/v1.17.0rc2 instead
Check https://github.com/slackapi/bolt-python/releases/tag/v1.17.0rc2 instead
Release Milestone: https://github.com/slackapi/bolt-python/milestone/75?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/74?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/73?closed=1
Nothing published for this version
Release Milestone: https://github.com/slackapi/bolt-python/milestone/72?closed=1
Nothing published for this version
Since this version, a new adapter that implements the ASGI standard is available. The novel adapter brings the following benefits to developers:
Since this version, a new adapter that implements the ASGI standard is available. The novel adapter brings the following benefits to developers:
The adapter is compatible with both App and AsyncApp. You can run both of the following app code by running uvicorn app:api --reload --port 3000 --log-level debug:
from slack_bolt import App
from slack_bolt.adapter.asgi import SlackRequestHandler
app = App()
@app.event("app_mention")
def handle_app_mentions(say):
say("What's up?")
api = SlackRequestHandler(app)
Here is an asyncio-based app:
from slack_bolt.async_app import AsyncApp
from slack_bolt.adapter.asgi.async_handler import AsyncSlackRequestHandler
app = AsyncApp()
@app.event("app_mention")
async def handle_app_mentions(say):
await say("What's up?")
api = AsyncSlackRequestHandler(app)
To learn more on the implementation and grab more code examples, please check @WilliamBergamin's pull request adding the feature: https://github.com/slackapi/bolt-python/pull/780
Release Milestone: https://github.com/slackapi/bolt-python/milestone/70?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/69?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/68?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/67?closed=1
Nothing published for this version
Release Milestone: https://github.com/slackapi/bolt-python/milestone/64?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/60?closed=1
Nothing published for this version
Release Milestone: https://github.com/slackapi/bolt-python/milestone/63?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/62?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/61?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/56?closed=1
__all__ to __init__.py files - Thanks @seratchRelease Milestone: https://github.com/slackapi/bolt-python/milestone/58?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/57?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/55?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/51?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/54?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/53?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/52?closed=1
Release Milestone: https://github.com/slackapi/bolt-python/milestone/50?closed=1
Your coding agent can read these notes before it upgrades. Set up the MCP server →