Guide · Slack

Migrate slackclient v2 to slack_sdk v3 (Python)

Slack's Python SDK changed its name at version 3: the PyPI project moved from slackclient to slack_sdk, and the import from slack to slack_sdk. Most calls keep working, but async code and dependency lists need real changes.

slack_sdk 3.0.0 was released on November 9, 2020. Slack's docs describe slackclient as in maintenance mode with slack_sdk as its successor, and the last slackclient release on PyPI, 2.9.4, tells you to use slack-sdk instead. Slack lists additional features in v3, including Socket Mode, OAuth flow support, the SCIM and Audit Logs APIs and retry handlers. Slack lists only four changes from 2.x and says existing code can run on v3 unchanged through a deprecated slack compatibility package. That package is useful for a staged rollout, but it maps slack.WebClient to a legacy class, so the import rename is where behavior actually changes. This guide follows Slack's v3 migration guide and SDK docs, linked at the end.

What changed

  • The PyPI project is slack_sdk (slack-sdk also works). Remove slackclient from your requirements.
  • Import from slack_sdk, for example from slack_sdk import WebClient. slack.* imports still work but emit a deprecation UserWarning.
  • slack_sdk has no required dependencies, so aiohttp is no longer installed for you.
  • WebClient is synchronous only. run_async=True and other aiohttp options are gone; use AsyncWebClient, or LegacyWebClient for the v2 behavior.
  • Errors live in slack_sdk.errors, signature verification in slack_sdk.signature, and the slack.web.classes models in slack_sdk.models.
  • For the legacy RTM API there is a new slack_sdk.rtm_v2.RTMClient next to the v2-style slack_sdk.rtm.RTMClient.

Migration steps

  1. Swap the package

    pip uninstall slackclient
    pip install slack_sdk

    Change requirements.txt or your lock file in the same commit. If you use AsyncWebClient, AsyncWebhookClient or LegacyWebClient, add aiohttp explicitly: slackclient pulled it in, slack_sdk does not. The v2-style RTM client in slack_sdk.rtm imports aiohttp as well. The current slack_sdk release declares Python 3.7 or later.

  2. Rename the imports

    Before

    from slack import WebClient
    from slack.errors import SlackApiError
    from slack.signature import SignatureVerifier
    from slack.web.classes.blocks import SectionBlock

    After

    from slack_sdk import WebClient
    from slack_sdk.errors import SlackApiError
    from slack_sdk.signature import SignatureVerifier
    from slack_sdk.models.blocks import SectionBlock

    Method calls do not change: client.chat_postMessage(channel=..., text=...) returns the same response object, and a failed call still raises SlackApiError with the error code in e.response["error"]. Slack's guide suggests a sed replacement of from slack with from slack_sdk for most files, and recommends changing slack.web.classes.* imports by hand, because those models moved to slack_sdk.models with a different layout. Webhooks follow the same pattern: from slack_sdk.webhook import WebhookClient.

  3. Move run_async code to AsyncWebClient

    Before

    from slack import WebClient
    from slack.errors import SlackApiError
    
    client = WebClient(token=os.environ["SLACK_API_TOKEN"], run_async=True)
    
    async def send_to_slack(channel, text):
        try:
            response = await client.chat_postMessage(channel=channel, text=text)
        except SlackApiError as e:
            log.warning("Slack error: %s", e.response["error"])

    After

    # requires: pip install aiohttp
    from slack_sdk.web.async_client import AsyncWebClient
    from slack_sdk.errors import SlackApiError
    
    client = AsyncWebClient(token=os.environ["SLACK_API_TOKEN"])
    
    async def send_to_slack(channel, text):
        try:
            response = await client.chat_postMessage(channel=channel, text=text)
        except SlackApiError as e:
            log.warning("Slack error: %s", e.response["error"])

    Slack recommends AsyncWebClient for anyone using run_async=True: it is a plain asyncio client, so every call is a coroutine you await. Code that drove the v2 client with loop.run_until_complete(future) can call asyncio.run(...) on an async function instead.

  4. Or keep v2 behavior with LegacyWebClient

    If you cannot rewrite async call sites yet, import the v2-compatible class under its own name. Slack says that apart from the name there is no breaking change in it, and it needs aiohttp installed:

    from slack_sdk.web.legacy_client import LegacyWebClient
    
    client = LegacyWebClient(token=os.environ["SLACK_API_TOKEN"], run_async=True)

    This removes the deprecation warning without changing behavior. Treat it as a stopping point rather than a destination; the synchronous WebClient and AsyncWebClient are where Slack's docs and new features are.

  5. Update RTM clients

    Before

    import slack
    
    @slack.RTMClient.run_on(event="message")
    def say_hello(**payload):
        data = payload["data"]
        web_client = payload["web_client"]
        if "Hello" in data["text"]:
            web_client.chat_postMessage(channel=data["channel"], text="Hi!", thread_ts=data["ts"])
    
    slack.RTMClient(token=os.environ["SLACK_BOT_TOKEN"]).start()

    After

    from slack_sdk.rtm_v2 import RTMClient
    
    rtm = RTMClient(token=os.environ["SLACK_BOT_TOKEN"])
    
    @rtm.on("message")
    def say_hello(client: RTMClient, event: dict):
        if "Hello" in event["text"]:
            client.web_client.chat_postMessage(channel=event["channel"], text="Hi!", thread_ts=event["ts"])
    
    rtm.start()

    slack_sdk.rtm.RTMClient keeps the v2 run_on decorator API if you need a smaller diff, but Slack does not recommend it for new development because it has issues that rtm_v2 fixes. Note that rtm_v2 does not support the rtm.start API method. The legacy RTM API also requires a classic Slack app with a plain bot scope; for new work Slack recommends the HTTP-based Events API.

How to find every affected call

Find the risky constructors before renaming anything, then every remaining slack import. ast-grep matches the syntax tree, so multi-line calls are found too:

sg run --pattern 'WebClient($$$BEFORE, run_async=$VALUE, $$$AFTER)' --lang python
sg run --pattern 'slack.WebClient($$$BEFORE, run_async=$VALUE, $$$AFTER)' --lang python
sg run --pattern 'from slack import $$$NAMES' --lang python
sg run --pattern 'import slack' --lang python
sg run --pattern 'from slack.errors import $$$NAMES' --lang python
sg run --pattern 'slack.RTMClient($$$ARGS)' --lang python

Imports from submodules such as slack.web.classes.blocks or slack.signature need one search each, or a plain text search for from slack.. If a file does import slack as $ALIAS, repeat the constructor searches with that alias. On Linux, sg may be a system command; ast-grep run is the same tool.

Testing the change

  • Check that slackclient is gone from the installed environment and lock file, not only from requirements.txt; pip show slackclient should find nothing.
  • Run the test suite once with python -W error::UserWarning -m pytest. Any remaining slack import raises its deprecation warning as an error and points at the file. Slack's compatibility package honors SLACKCLIENT_SKIP_DEPRECATION to silence the warning, so make sure it is not set in that run.
  • Fix mocks. Patches on slack.WebClient.chat_postMessage do not intercept slack_sdk.WebClient; patch the class your code now imports, or inject a fake client.
  • Import every module in the deployed image, not just in development. A missing aiohttp only shows up as an ImportError where the async client or RTM module is loaded.

Automating it

Slack does not ship a codemod. Its migration guide suggests a sed one-liner for most from slack imports and manual edits for slack.web.classes. As step 2 explains, a blind rename is also exactly how run_async code breaks.

Versyka finds the call sites deterministically with ast-grep, rewrites them along with the code around them (async clients, RTM handlers, mocks, requirements), then runs your repository's own tests in an isolated virtualenv inside a disposable sandbox. It opens a pull request only if they pass.

Was this guide helpful?

Check your own repository

Paste your package.json, requirements.txt or pyproject.toml into the free upgrade check to see which API SDKs are behind, with no account. Or let Versyka run this migration on one repository and open a pull request that has already passed your tests.

Sources