Slack Bolt for Python -- a framework for building Slack apps in Python.
-
Foundation: Built on top of
slack_sdk(seepyproject.tomlconstraints). -
Execution Models: Supports both synchronous (
App) and asynchronous (AsyncAppusingasyncio) execution. Async mode requiresaiohttpas an additional dependency. -
Framework Adapters: Features built-in adapters for web frameworks (Flask, FastAPI, Django, Tornado, Pyramid, and many more) and serverless environments (AWS Lambda, Google Cloud Functions).
-
Python Version: Requires Python 3.7+ as defined in
pyproject.toml. -
Repository: https://github.com/slackapi/bolt-python
-
Documentation: https://docs.slack.dev/tools/bolt-python/
-
Current version: defined in
slack_bolt/version.py(referenced bypyproject.tomlvia[tool.setuptools.dynamic])
You can verify the venv is active by checking echo $VIRTUAL_ENV. If tools like black, flake8, mypy or pytest are not found, ask the user to activate the venv.
A python virtual environment (venv) should be activated before running any commands.
# Create a venv (first time only)
python -m venv .venv
# Activate
source .venv/bin/activate
# Install all dependencies
./scripts/install.shBefore considering any work complete, you MUST run these commands in order and confirm they all pass:
./scripts/format.sh --no-install # 1. Format
./scripts/lint.sh --no-install # 2. Lint
./scripts/run_tests.sh <relevant> # 3. Run relevant tests (see Testing below)
./scripts/run_mypy.sh --no-install # 4. Type checkTo run everything at once (installs deps + formats + lints + tests + typechecks):
./scripts/install_all_and_run_tests.shAlways use the project scripts instead of calling pytest directly:
# Run a single test file
./scripts/run_tests.sh tests/scenario_tests/test_app.py
# Run a single test function
./scripts/run_tests.sh tests/scenario_tests/test_app.py::TestApp::test_name# Format -- Black, configured in pyproject.toml
./scripts/format.sh --no-install
# Lint -- Flake8, configured in .flake8
./scripts/lint.sh --no-install
# Type check -- mypy, configured in pyproject.toml
./scripts/run_mypy.sh --no-installWhen modifying any sync module, you MUST also update the corresponding async module (and vice versa). This is the most important convention in this codebase.
Almost every module has both a sync and async variant. Async files use the async_ prefix alongside their sync counterpart:
slack_bolt/middleware/custom_middleware.py # sync
slack_bolt/middleware/async_custom_middleware.py # async
slack_bolt/context/say/say.py # sync
slack_bolt/context/say/async_say.py # async
slack_bolt/listener/custom_listener.py # sync
slack_bolt/listener/async_listener.py # async
Modules that come in sync/async pairs:
slack_bolt/app/--app.py/async_app.pyslack_bolt/middleware/-- every middleware has anasync_counterpartslack_bolt/listener/--listener.py/async_listener.py, plus error/completion/start handlersslack_bolt/listener_matcher/--builtins.py/async_builtins.pyslack_bolt/context/-- each subdirectory (e.g.,say/,ack/,respond/) hasasync_variantsslack_bolt/kwargs_injection/--args.py/async_args.py,utils.py/async_utils.py
Adapters are an exception: Most adapters are sync-only or async-only depending on the framework. Async-native frameworks (FastAPI, Starlette, Sanic, Tornado, ASGI, Socket Mode) have async_handler.py. Sync-only frameworks (Flask, Django, Bottle, CherryPy, Falcon, Pyramid, AWS Lambda, Google Cloud Functions, WSGI) have handler.py.
Middleware is the project's preferred approach for cross-cutting concerns. Before adding logic to individual listeners or utility functions, consider whether it belongs as a built-in middleware in the framework.
When to add built-in middleware:
- Cross-cutting concerns that apply to many or all requests (logging, metrics, observability)
- Request validation, transformation, or enrichment
- Authorization extensions beyond the built-in
SingleTeamAuthorization/MultiTeamsAuthorization - Feature-level request handling (the
Assistantmiddleware inslack_bolt/middleware/assistant/assistant.pyis the canonical example -- it intercepts assistant thread events and dispatches them to registered sub-listeners)
How to add built-in middleware:
- Subclass
Middleware(sync) and implementprocess(self, *, req, resp, next). Callnext()to continue the chain. - Subclass
AsyncMiddleware(async) and implementasync_process(self, *, req, resp, next). Callawait next()to continue. - Export from
slack_bolt/middleware/__init__.py(sync) andslack_bolt/middleware/async_builtins.py(async). - Register the middleware in
App.__init__()(slack_bolt/app/app.py) andAsyncApp.__init__()(slack_bolt/app/async_app.py) where the default middleware chain is assembled.
Canonical example: AttachingFunctionToken (slack_bolt/middleware/attaching_function_token/) is a good small middleware to follow -- it has a clean sync/async pair, a focused process() method, and is properly exported and registered in the app's middleware chain.
The core package depends ONLY on slack_sdk (defined in pyproject.toml). Never add runtime dependencies to pyproject.toml. Additional dependencies go in the appropriate requirements/*.txt file.
Incoming requests flow through a middleware chain before reaching listeners:
- SSL Check -> Request Verification (signature) -> URL Verification -> Authorization (token injection) -> Ignoring Self Events -> Custom middleware
- Listener Matching --
ListenerMatcherimplementations check if a listener should handle the request - Listener Execution -- listener-specific middleware runs, then
ack()is called, then the handler executes
For FaaS environments (process_before_response=True), long-running handlers execute as "lazy listeners" in a thread pool after the ack response is returned.
App/AsyncApp(slack_bolt/app/) -- Central class. Registers listeners via decorators (@app.event(),@app.action(),@app.command(),@app.message(),@app.view(),@app.shortcut(),@app.options(),@app.function()). Dispatches incoming requests through middleware to matching listeners.Middleware(slack_bolt/middleware/) -- Abstract base withprocess(req, resp, next). Built-in: authorization, request verification, SSL check, URL verification, assistant, self-event ignoring.Listener(slack_bolt/listener/) -- Has matchers, middleware, and an ack/handler function.CustomListeneris the main implementation.ListenerMatcher(slack_bolt/listener_matcher/) -- Determines if a listener handles a given request. Built-in matchers for events, actions, commands, messages (regex), shortcuts, views, options, functions.BoltContext(slack_bolt/context/) -- Dict-like object passed to listeners withclient,say(),ack(),respond(),complete(),fail(), plus event metadata (user_id,channel_id,team_id, etc.).BoltRequest/BoltResponse(slack_bolt/request/,slack_bolt/response/) -- Request/response wrappers. Request hasmodeof "http" or "socket_mode".
Listeners receive arguments by parameter name. The framework inspects function signatures and injects matching args: body, event, action, command, payload, context, client, ack, say, respond, logger, complete, fail, agent, etc. Defined in slack_bolt/kwargs_injection/args.py.
Each adapter in slack_bolt/adapter/ converts between a web framework's request/response types and BoltRequest/BoltResponse. Adapters exist for: Flask, FastAPI, Django, Starlette, Sanic, Bottle, Tornado, CherryPy, Falcon, Pyramid, AWS Lambda, Google Cloud Functions, Socket Mode, WSGI, ASGI, and more.
BoltAgent (slack_bolt/agent/) provides chat_stream(), set_status(), and set_suggested_prompts() for AI-powered agents. Assistant middleware (slack_bolt/middleware/assistant/) handles assistant thread events.
Each context utility lives in its own subdirectory under slack_bolt/context/:
slack_bolt/context/my_util/
__init__.py
my_util.py # sync implementation
async_my_util.py # async implementation
internals.py # shared logic (optional)
Then wire it into BoltContext (slack_bolt/context/context.py) and AsyncBoltContext (slack_bolt/context/async_context.py).
- Create
slack_bolt/adapter/<framework>/ - Add
__init__.pyandhandler.py(orasync_handler.pyfor async frameworks) - The handler converts the framework's request to
BoltRequest, callsapp.dispatch(), and convertsBoltResponseback - Add the framework to
requirements/adapter.txtwith version constraints - Add adapter tests in
tests/adapter_tests/(sync) ortests/adapter_tests_async/(async)
- Add the new arg to
slack_bolt/kwargs_injection/args.pyandasync_args.py - Update the
Argsclass with the new property - Populate the arg in the appropriate context or listener setup code
- Request Verification: The built-in
RequestVerificationmiddleware validatesx-slack-signatureandx-slack-request-timestampon every incoming HTTP request. Never disable this in production. It is automatically skipped forsocket_moderequests. - Tokens & Secrets:
SLACK_SIGNING_SECRETandSLACK_BOT_TOKENmust come from environment variables. Never hardcode or commit secrets. - Authorization Middleware:
SingleTeamAuthorizationandMultiTeamsAuthorizationverify tokens and inject an authorizedWebClientinto the context. Do not bypass these. - Tests: Always use mock servers (
tests/mock_web_api_server/) and dummy values. Never use real tokens in tests.
The core package has a single required runtime dependency: slack_sdk (defined in pyproject.toml). Do not add runtime dependencies.
requirements/ directory structure:
async.txt-- async runtime deps (aiohttp,websockets)adapter.txt-- all framework adapter deps (Flask, Django, FastAPI, etc.)testing.txt-- test runner deps (pytest,pytest-asyncio, includesasync.txt)testing_without_asyncio.txt-- test deps without async (pytest,pytest-cov)adapter_testing.txt-- adapter-specific test deps (moto,boddle,sanic-testing)tools.txt-- dev tools (mypy,flake8,black)
When adding a new dependency: add it to the appropriate requirements/*.txt file with version constraints, never to pyproject.toml dependencies (unless it's a core runtime dep, which is very rare).
tests/scenario_tests/-- Integration-style tests with realistic Slack payloadstests/slack_bolt/-- Unit tests mirroring the source structuretests/adapter_tests/andtests/adapter_tests_async/-- Framework adapter teststests/mock_web_api_server/-- Mock Slack API server used by tests- Async test variants use
_asyncsuffix directories
Where to put new tests: Mirror the source structure. For slack_bolt/middleware/foo.py, add tests in tests/slack_bolt/middleware/test_foo.py. For async variants, use the _async suffix directory or file naming pattern. Adapter tests go in tests/adapter_tests/ (sync) or tests/adapter_tests_async/ (async).
Mock server: Many tests use tests/mock_web_api_server/ to simulate Slack API responses. Look at existing tests for usage patterns rather than making real API calls.
GitHub Actions (.github/workflows/ci-build.yml) runs on every push to main and every PR:
- Lint --
./scripts/lint.shon latest Python - Typecheck --
./scripts/run_mypy.shon latest Python - Unit tests -- full test suite across Python 3.7--3.14 matrix
- Code coverage -- uploaded to Codecov
- PRs target the
mainbranch - You MUST run
./scripts/install_all_and_run_tests.shbefore submitting - PR template (
.github/pull_request_template.md) requires: Summary, Testing steps, Category checkboxes (App,AsyncApp, Adapters, Docs, Others) - Requirements: CLA signed, test suite passes, code review approval
- Commits should be atomic with descriptive messages. Reference related issue numbers.