What is a build backend?
A build backend is a library that turns your Python source code into distributable packages: wheels and sdists. It reads your project metadata, processes your source tree, and produces the archives that get uploaded to PyPI or installed locally.
Build backends work in tandem with build frontends. The frontend is the tool a developer invokes (uv, pip, python -m build). It typically creates an isolated build environment, installs the backend and its dependencies, and calls standardized hook functions. The backend does the actual work of producing artifacts. All PEP 517-compliant backends implement the same hook API; the mandatory hooks are build_wheel and build_sdist, so any compliant frontend can invoke any backend without special-case code.
Why build backends exist
Before PEP 517 was accepted in 2017, the standard way to build a Python package was through a setup.py file, an interface built on distutils and usually powered by setuptools. Alternative build systems existed, but they typically had to wrap or mimic the setuptools interface to work with existing installers. PEP 517 solved this by defining a standard hook API that any backend can implement and any frontend can call.
How the frontend finds the backend
The [build-system] table in pyproject.toml declares which backend a project uses:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"requires lists packages the frontend must install before building. build-backend names a Python object using module[:object] syntax (e.g., "hatchling.build"). The frontend imports that object and calls its hook functions to produce artifacts.
Choosing a backend
The choice matters less than it used to. All mainstream backends produce valid wheels and sdists, and all work with standard frontends. The differences come down to what extra capabilities you need.
setuptools is the oldest backend and supports the widest range of build scenarios, including C extensions, data files, and namespace packages. It carries legacy complexity but handles cases that newer tools sometimes skip.
Hatchling is a modern backend with a plugin system for versioning, build hooks, and file inclusion. It was the original default for uv init --lib projects; many existing uv projects still use it.
uv_build is uv’s own backend, and has been the default for packaged uv projects (created with uv init --lib or uv init --package) since July 2025. It currently supports pure Python packages only, with minimal configuration.
flit_core does one thing: build pure Python packages. No plugins, no hooks, no extension support. For projects that fit that description, the simplicity is the point.
maturin builds Python packages from Rust code (via PyO3, cffi, or uniffi). scikit-build-core is a CMake-based backend for C/C++/Fortran extensions, with support for tools like pybind11, nanobind, and Cython. While setuptools can also compile C extensions, these specialized backends provide deeper toolchain integration for complex native builds.
Tip
For pure Python projects, any of the general-purpose backends will work. Pick the one your project manager defaults to and move on. You can always switch later since the interface is standardized.
What the backend does not do
Build backends produce artifacts. They do not resolve dependencies, manage virtual environments, upload to PyPI, or run tests. Those responsibilities belong to other tools in the chain: the build frontend manages build isolation and build requirements, install tools like pip or uv handle runtime dependency resolution, twine or uv publish handles uploads, and your test runner handles testing. The narrow scope is intentional, following the same separation of concerns that PEP 517 was designed to create.
Also Mentioned In
- uv: A Complete Guide to Python's Fastest Package Manager
- How Python's RFC Process Paved the Way for uv, Ruff, and Ty
- uv 0.8 Release: Automatic Python Installation to PATH
- The uv build backend is now stable
- build: Python Package Build Frontend
- Flit: Python Package Build and Publish Tool
- Hatch: Python Project Manager
- How to add dynamic versioning to uv projects
- How to Fix ModuleNotFoundError: No module named 'numpy' During pip Install
- How to migrate from setup.py to pyproject.toml
- PDM: Python Package and Dependency Manager
- sdist: Python Source Distribution Format
- setuptools: Python Package Build Backend
- Understanding uv init Project Types
- What is a Python package?
- What is PEP 660?
- Wheel: Python Binary Distribution Format
- Why did uv originally use Hatch as a build backend?
Get Python tooling updates
Subscribe to the newsletter