Library / SDK
Rapptz/discord.py avatar
Rapptz/discord.py

discord.py's install is easy and its dependency ceilings are not

An API wrapper for Discord written in Python.

16,210 stars3,898 forksPythonMIT

At a glance

What is it?
discord.py wraps the Discord API in an async Python library with a commands extension on top, and the README presents it as a one-line install. The parts worth reading first are a voice extra that needs system packages pip cannot install, ceilings at aiohttp 4 and PyNaCl 1.7, and a build that derives its version from discord/__init__.py.
Who is it for?
discord.py suits a team writing a text bot that wants an async API, shipped typing information and a commands extension without writing the HTTP layer. It is the wrong pick if you need voice on a Linux host without development headers, or if your platform cannot run Python 3.8 or higher.
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 68 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 November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • Rapptz/discord.py16,210 ★ · +22
Star history
Repository2026-09-212026-11-14
Rapptz/discord.py16.2k16.2k

22 Stars in the last 54 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The two README examples are two different bot APIs, not two versions of one

discord.py describes itself as an API wrapper for Discord written in Python, and the quick example and the bot example build genuinely different things. The first subclasses `discord.Client` and overrides `on_ready` and `on_message` directly, matching content with a plain equality test against the string `ping`. The second imports `discord.ext.commands`, builds a `commands.Bot` with `command_prefix='>'`, and attaches behaviour through a `@bot.command()` decorator. Both finish with a `run` call taking your token.

The difference is not cosmetic. The low-level version dispatches on whatever text arrives, so a message reading `ping ` with a trailing space, or `!ping`, does not match and the bot stays silent. The extension version registers a named command behind a prefix and hands the invocation context to your function, so matching happens for you and adding coercion or error handling does not mean rewriting dispatch.

Pick one on purpose. The `discord.Client` form is the floor the library sits on, and the commands extension is the layer built on top of it.

python
import discord
from discord.ext import commands

intents = discord.Intents.default()
intents.message_content = True
bot = commands.Bot(command_prefix='>', intents=intents)

@bot.command()
async def ping(ctx):
    await ctx.send('pong')

bot.run('token')

The project names three key features: a modern Pythonic API using `async` and `await`, proper rate limit handling, and being optimised in both speed and memory. The README gives no benchmark for any of the three.

One hard requirement, then a pip command that differs by platform

The README states the requirement before anything else: Python 3.8 or higher. What follows is three ways to install, and they differ on which interpreter they invoke rather than on what they install.

bash
# Linux/macOS
python3 -m pip install -U discord.py

# Windows
py -3 -m pip install -U discord.py

macOS and Linux go through `python3`; Windows goes through `py -3`. The `-U` flag is on every command, so upgrading is the same string you installed with.

A note attached to that step is easy to skip and expensive to ignore: install into a virtual environment, especially on Linux, where the system Python is externally managed and restricts which packages you can install on it. On a current distribution image that restriction alone can stop the plain command, and the remedy is a virtual environment rather than another flag.

For a checkout of the source rather than the published package, three commands. Note that the extras marker here attaches to a path rather than to a package name:

bash
$ git clone https://github.com/Rapptz/discord.py
$ cd discord.py
$ python3 -m pip install -U .[voice]

Voice support needs system packages that pip cannot install for you

The voice extra is not only a Python dependency. `discord.py[voice]` resolves `PyNaCl>=1.6.0,<1.7` and `davey>=0.1.0`, and then the README adds a step pip cannot perform on your behalf: on Linux you must install `libffi-dev`, or `libffi-devel` on some systems, and `python-dev`, for example `python3.8-dev` for Python 3.8, through your package manager such as `apt` or `dnf`, and only after that run the pip command.

The ordering is the part to remember. The system packages come first.

So `discord.py[voice]` is not a portable install string. On a slim container image, or on any Linux host without development headers, that command fails for a reason that has nothing to do with discord.py itself. If you only need text, the plain install carries no such requirement, which is why the README puts it first and describes the extras as the branch that adds voice.

The quotes around discord.py[voice] are a shell requirement

Put the two voice commands side by side:

bash
# Linux/macOS
python3 -m pip install -U "discord.py[voice]"

# Windows
py -3 -m pip install -U discord.py[voice]

The argument is identical. The quoting is not. On Linux and macOS the extras marker is wrapped in double quotes; on Windows it is bare. That is a shell detail rather than a discord.py rule. Square brackets are a glob pattern, so an unquoted `discord.py[voice]` can be expanded by the shell before pip ever receives it, and quoting is what tells the shell to pass the string through unchanged.

Small as it is, this is the kind of detail that costs an afternoon when a line you copied behaves differently in your terminal than in the README. Copy the line for your platform as written instead of normalising the two into one, and quote the marker if you put the install into a script.

Two dependency ceilings that decide when an upgrade breaks

The dependency file holds two lines, and both of them bound a major version:

text
aiohttp>=3.7.4,<4
audioop-lts; python_version>='3.13'

The `aiohttp` floor is 3.7.4 and the ceiling is 4, so the HTTP layer this library speaks to Discord over will not move forward when aiohttp 4 arrives. The second line is the more telling one. `audioop-lts` is conditioned on `python_version>='3.13'`, and that marker only has meaning if the package gets installed on 3.13, where the standard library audio module it stands in for is gone.

The voice extra adds a tighter ceiling still. `PyNaCl>=1.6.0,<1.7` excludes 1.7 and everything after it, so no release outside that range resolves from a plain install, while `davey>=0.1.0` carries no upper bound at all. Two dependencies in one extra, pinned with opposite strictness, which tells you something about how each is expected to move.

For a reader, the practical shape of upgrade planning follows from those three bounds. You will learn about aiohttp 4 or PyNaCl 1.7 from a failed resolution or from another package holding discord.py back, not from a release note. `pyproject.toml` is where the optional-dependency definitions live if you need to widen anything.

The version number is scraped out of discord/__init__.py during the build

The manifest lists `version` and `dependencies` under `dynamic`, so neither value is written down in it. Both are resolved at build time. Dependencies come from `requirements.txt`. The version is extracted by `setup.py`, which opens `discord/__init__.py` and matches a single assignment line with a regular expression. If the match comes back empty, the script raises `RuntimeError` with the message `version is not set`.

There is a second step for pre-release builds. When the extracted version ends in `a`, `b` or `rc`, the script shells out to git twice: once for `git rev-list --count HEAD` and once for `git rev-parse --short HEAD`, appending the commit count and then a `+g` with the short hash. A release candidate built from a checkout therefore ends up with a version string that names the exact commit behind it.

Both git calls sit inside a `try` block that swallows every exception, so a build without a usable git checkout quietly yields a pre-release version carrying no commit suffix instead of stopping. That is worth knowing the first time a dev install and an installed-from-source copy report different-looking versions for what is nominally the same tree.

Classifiers stop at Python 3.12 while the dependency file already knows 3.13

The manifest requires Python 3.8 or higher, and its classifiers enumerate Python 3.8 through 3.12. The dependency file has already moved past that list, because `audioop-lts` is conditioned on `python_version>='3.13'`, which only matters on an interpreter newer than anything the classifiers name. Runtime support therefore extends one version beyond what the metadata advertises.

That gap has effects beyond tidiness. Classifiers are what an index and dependency tooling read when deciding compatibility, and the README's supported-versions badge is generated from them, so the badge describes 3.8 to 3.12 while the code carries a shim for something newer. A team gating a CI matrix on what the manifest claims will skip the very version the dependency file was written for.

The same manifest lists `Typing :: Typed` and `Operating System :: OS Independent`, and it points `readme` at `README.rst` with the `text/x-rst` content type. That has one concrete consequence worth knowing: the package description published to the index is built from the English README, while `README.ja.rst` sits in the repository root without being the file used.

The docs extra pins sphinx 4.4.0 and keeps two unfinished TODOs

The optional `docs` dependency is the most tightly pinned thing in the repository. `sphinx==4.4.0` is an exact pin, and beside it sit exact-pinned `sphinxcontrib` packages: websupport at 1.2.4, applehelp at 1.0.4, devhelp at 1.0.2, htmlhelp at 2.0.1, jsmath at 1.0.1, qthelp at 1.0.3 and serializinghtml at 1.1.5, along with `sphinxcontrib_trio==1.1.2` and `sphinx-inline-tabs==2023.4.21`. Of the entries shown, only `typing-extensions>=4.3,<5` moves within a range.

Two comments sit in that block, and neither is finished work in an issue. One says to bump these when migrating to a newer Sphinx version. Another says to remove one of the packages when a later migration happens. Both are stated inside the dependency list, which means anyone who edits the pins has to read the reasoning in place rather than in a commit message.

The consequence for a reader is that the documentation is not built from a current toolchain, and anyone rebuilding it locally inherits the entire pin set along with whatever conflicts those old versions have with a newer interpreter. The README points you at the published documentation instead of a local build, which given the pins is the right default. The repository root also carries a `.readthedocs.yml`.

Editorial conclusion

discord.py suits a team writing a text bot that wants an async API, shipped typing information and a commands extension without writing the HTTP layer. It is the wrong pick if you need voice on a Linux host without development headers, or if your platform cannot run Python 3.8 or higher. Before pinning, read requirements.txt for the ceilings at aiohttp 4 and PyNaCl 1.7, and read discord/__init__.py to see the version string your build will scrape.

Frequently asked questions

What does discord.py do?

It is a Python wrapper for the Discord API written around async and await, with a lower-level client class and a commands extension on top. The project lists proper rate limit handling among its key features.

Is discord.py outdated?

The last push to the repository was on 2026-09-07. The manifest requires Python 3.8 or higher and its classifiers list Python 3.8 through 3.12, while requirements.txt already carries a dependency conditioned on Python 3.13 and later.

Is discord.py a framework?

It presents itself as an API wrapper. The quick example subclasses discord.Client and overrides on_ready and on_message, while the bot example uses discord.ext.commands with a command prefix and a command decorator.

how to use discord.py

Both README examples build intents with discord.Intents.default(), set intents.message_content to True, and pass them into the client or bot. More examples sit in the repository examples directory, including app_commands, modals and views folders.

how to install discord.py on windows

The README gives `py -3 -m pip install -U discord.py` for Windows, and `py -3 -m pip install -U discord.py[voice]` when you want voice support. The extras argument is written without quotes on Windows.

how to install discord py on linux

The command is `python3 -m pip install -U discord.py`. The README recommends a virtual environment, because on Linux the system Python is externally managed and restricts which packages you can install on it. Voice support additionally needs libffi-dev and python-dev installed through apt or dnf beforehand.

Official sources

  1. Issues
  2. License: MIT
  3. Project website
  4. Rapptz/discord.py on GitHub
  5. README
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/rapptz-discord-py.svg)](https://hysenlabs.com/projects/rapptz-discord-py)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/rapptz-discord-py.svg)](https://hysenlabs.com/projects/rapptz-discord-py)

Was this useful?