Self-hosted service
slackapi/bolt-python avatar
slackapi/bolt-python

Bolt for Python: one App object, eight listener methods, two delivery modes

A framework to build Slack apps using Python

1,329 stars293 forksPythonMIT

At a glance

What is it?
slackapi/bolt-python routes Slack events, actions, slash commands and modals to named listener functions over either HTTP or Socket Mode. The listener surface is small and well documented, while the packaging file still claims Python 3.7 while classifying support up to 3.14.
Who is it for?
Adopt slackapi/bolt-python when the Slack surface is events, actions, slash commands or modals and your team already writes Python, because routing, acknowledgement and replies are handled for you. Do not adopt it as a general HTTP framework, and do not mix an HTTP listener with a socket handler in one process without reading the source, since the two run modes are documented as separate programs.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 6 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Slack apps without hand written request routing

Every Slack app ends up solving the same four problems. A request arrives over HTTP, its signature has to be checked against a shared secret, the body has to reach the right handler, and the handler has to answer before Slack stops waiting. slackapi/bolt-python takes that layer off your hands, and it targets teams writing internal tools, chatops commands and workflow bots rather than people building general web services.

The entry point is a top-level export named App. Called with no arguments it expects credentials from the environment, and the README keeps those exports as comments inside the first Python block, so the snippet below imports and constructs without setting anything up by itself.

python
from slack_bolt import App

# export SLACK_SIGNING_SECRET=***
# export SLACK_BOT_TOKEN=xoxb-***
app = App()

# Add functionality here

if __name__ == "__main__":
    app.start(3000)  # POST http://localhost:3000/slack/events

That trailing comment is the whole server configuration for HTTP mode. One integer binds the port, and Slack posts inbound traffic to `http://localhost:3000/slack/events`. A bare constructor reads two environment variables and owns nothing else, which is the shortest path from an empty file to a bot that answers in a channel.

Eight listener methods split Slack interactivity by request type

Registration is one method call per kind of request, and the framework counts eight of them. Each returns a decorator, so the same expression works as a bare call or wrapped above a function body.

python
app.action(action_id)(fn)
app.command(command_name)(fn)
app.event(event_type)(fn)
app.function(callback_id)(fn)
app.message([pattern ,])(fn)
app.options(action_id)(fn)
app.shortcut(callback_id)(fn)
app.view(callback_id)(fn)

That block is written as direct calls, and the line after it calls the decorator form the recommended style, so the two are the same mechanism shown twice.

Every listener receives the same named arguments whatever triggered it: `body`, `payload`, `context`, `ack`, `respond`, `say` and `client`. `payload` is a subset of `body` and is also exposed under a type-specific alias, which is why a message listener can use `payload` and `message` interchangeably. Data that never travels inside the payload, such as `trigger_id` and `authorizations`, is reachable only through `body`. Prefer a single object and the `args` argument hands over a `slack_bolt.kwargs_injection.Args` instance holding everything for that event, and the shortest way to learn what a payload contains is still to log it.

ack is listed for actions but not for event listeners

The ack argument is the one you cannot skip. Slack treats an action, shortcut, view submission, slash command or options request as unanswered until the framework acknowledges it, and the argument table ties ack to exactly those five request types. Events API events are not in that list, so an event listener never has to call it.

The respond utility is narrower again. It works only when the incoming request carries a `response_url`, which covers shortcuts, actions and slash commands, and it posts the reply back to where the user clicked even after the listener has returned. The say utility fails on a different condition: it needs a `channel_id` on the event, which is why it is tied to `message` events in practice, and it accepts a plain string or a dictionary of blocks. `client` is a Web API client bound to the token that belongs to the event itself, and that is the piece that matters for org-wide installs where several workspaces share one running process.

The HTTP path needs a signing secret and a public tunnel

Installing is a virtualenv and one package.

bash
# Python 3.7+ required
python -m venv .venv
source .venv/bin/activate

pip install -U pip
pip install slack_bolt

Running the finished script needs the same two exports again plus a tunnel, because Slack has to reach your machine across the internet:

bash
export SLACK_SIGNING_SECRET=***
export SLACK_BOT_TOKEN=xoxb-***
python app.py

# in another terminal
ngrok http 3000

Leave out `SLACK_SIGNING_SECRET` and the signature check has nothing to compare against. Leave out the tunnel and `http://localhost:3000/slack/events` stays unreachable from Slack, which is the friction the other run mode was written to remove. The bot token in both blocks starts with `xoxb-`, and the placeholder after the equals sign is what your Slack app page issues.

Socket Mode swaps the tunnel for an app level token

Inbound traffic over a websocket removes both the signing secret and ngrok. A second token carries the `connections:write` scope, and a handler class in `slack_bolt.adapter.socket_mode` drives the connection.

python
import os
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

app = App(token=os.environ["SLACK_BOT_TOKEN"])

if __name__ == "__main__":
    handler = SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"])
    handler.start()

The two omissions are stated outright: SLACK_SIGNING_SECRET is not required, and running ngrok is not required. Two details change with the mode. The constructor here is passed `token=` explicitly, unlike the bare `App()` in the HTTP example, and the export list swaps the signing secret for `SLACK_APP_TOKEN`, whose value starts with `xapp-` rather than `xoxb-`. Listener code stays identical between modes, which is the point of the handler taking an app object instead of a transport.

pyproject.toml pins slack_sdk and keeps a universal wheel flag

The install step pulls exactly one dependency, and it sets the real floor for the project:

toml
requires-python = ">=3.7"
dependencies = ["slack_sdk>=3.38.0,<4"]

Two claims in the same file disagree with each other. The classifier list runs from Python 3.7 through 3.14 while `requires-python` still stops at 3.7, and the wheel section keeps `universal = true`, a flag that historically marked a build as usable on Python 2 and Python 3 alike, which no longer matches a package with a 3.7 floor. The project's own version is not written down at all:

toml
version = { attr = "slack_bolt.version.__version__" }

Nothing in the packaging metadata tells you which build you installed without importing the package and reading that attribute. Tooling shares the file: `line-length = 125`, a lint select of `select = ["E", "W", "F", "D"]` applying google docstring rules, and a comment explaining that the D category has to stay a bare category select because naming individual D codes would override the convention. Pytest writes to `log_file = "logs/pytest.log"` at DEBUG level, which explains the `logs/` directory sitting in the top level of the repository next to `tests/` and `requirements/`.

Adapter examples span Flask, Django, ASGI and serverless targets

The repository ships a wide `examples/` tree rather than one canonical sample, and reading two adapters side by side shows how little listener code changes between them. Server adapters have their own directories: `examples/flask/`, `examples/django/`, `examples/asgi/`, `examples/fastapi/` and `examples/falcon/`. Deployment targets get the same treatment, with `examples/docker/`, `examples/heroku/`, `examples/aws_lambda/` and `examples/google_cloud_run/` alongside older ones such as `examples/google_app_engine/`.

Single file samples sit beside those directories. `examples/app.py` is the starting point, `examples/events_app.py` narrows to events, and the async variants `examples/async_app.py` and `examples/lazy_async_modals_app.py` cover the coroutine path, with `examples/assistants/` covering Slack assistant features. The repository topics repeat the same claim in machine readable form, listing aiohttp, asyncio, django, fastapi, flask, socket-mode, web-api, websocket and chatops.

Two top level entries are easy to miss because they have nothing to do with packaging: `AGENTS.md` and a `.claude/` directory, versioned in the source tree beside `SECURITY.md`, `MANIFEST.in`, `setup.cfg` and `codecov.yml`. A reader looking for a single getting started path will find `examples/getting_started/` and the external guide the README links instead.

Two run modes, one file, and no documented rollback

HTTP mode and Socket Mode are written as two separate programs. Nothing in the README shows one `app.py` serving both, and no text says what happens if an HTTP listener on port 3000 and a socket handler start in the same process, so treat that combination as undefined rather than supported. Authorization for org-wide installs is the same story: `examples/app_authorize.py` exists in the tree, but the README never walks through the flow.

Upgrade cost in code is low and policy is undocumented. The dependency stays below `slack_sdk` 4, the repository ships under the MIT licence and is not archived, and the last push was on 2026-09-08. The README documents no deprecation window, no supported Python matrix beyond the 3.7 floor, and no rollback procedure, so check your own pin before moving a production app forward. One more silence matters in production: the ack function returns a promise that resolves when complete, yet no acknowledgement timeout is stated anywhere in the documented configuration, so a listener that never calls it leaves you reading `logs/` output instead of a documented error.

Editorial conclusion

Adopt slackapi/bolt-python when the Slack surface is events, actions, slash commands or modals and your team already writes Python, because routing, acknowledgement and replies are handled for you. Do not adopt it as a general HTTP framework, and do not mix an HTTP listener with a socket handler in one process without reading the source, since the two run modes are documented as separate programs. Before shipping, read pyproject.toml: the package needs slack_sdk>=3.38.0,<4, declares 3.7 as its floor while listing classifiers through 3.14, and keeps its own version outside the metadata.

Frequently asked questions

Can slackapi/bolt-python receive Slack events without a public URL?

Yes, through Socket Mode. SocketModeHandler in slack_bolt.adapter.socket_mode takes the app plus an app level token carrying the connections:write scope, and the README states that SLACK_SIGNING_SECRET is not required and running ngrok is not required in that mode.

Which Slack requests need an ack call in slackapi/bolt-python?

The argument table ties ack to actions, shortcuts, view submissions, slash commands and options requests, and it must be called to acknowledge the incoming request. Events API events are not in that list, so an event listener does not call it.

What does slackapi/bolt-python need before you install it?

A virtualenv and a Python interpreter, since the setup block states Python 3.7+ required. The package name is slack_bolt, installed with pip install slack_bolt, and pyproject.toml declares a single dependency, slack_sdk>=3.38.0,<4.

Can I run slackapi/bolt-python inside a Flask or Django application?

Yes, the examples tree ships separate adapters for both, at examples/flask/ and examples/django/, alongside examples/asgi/, examples/fastapi/ and examples/falcon/. The App object and its listener methods stay the same, only the surrounding server changes.

How do I find out which version of slackapi/bolt-python is installed?

pyproject.toml does not record it, because the version is read from slack_bolt.version.__version__ through an attribute reference rather than written in the file. You have to import the package and read that attribute to learn the build number.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. slackapi/bolt-python on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/slackapi-bolt-python.svg)](https://hysenlabs.com/projects/slackapi-bolt-python)

Was this useful?