# What is PEP 517/518 compatibility?


[PEP 518](https://peps.python.org/pep-0518/) introduced [pyproject.toml](https://pydevtools.com/handbook/reference/pyproject.toml.md) in 2016 and defined the `[build-system]` table for declaring build-time dependencies in a tool-agnostic way. [PEP 517](https://peps.python.org/pep-0517/) followed in 2017 and defined the hook API that every build backend implements and every build frontend calls. Together they replaced the setup.py-only build model and made it possible to use [Hatchling](https://pydevtools.com/handbook/reference/hatch.md), [flit_core](https://pydevtools.com/handbook/reference/flit.md), [poetry-core](https://pydevtools.com/handbook/reference/poetry.md), uv_build, maturin, or any other backend with the same install commands.

## The problem the PEPs solved

Before PEP 518, every Python package was built by running `setup.py`, a Python script that imported [setuptools](https://pydevtools.com/handbook/reference/setuptools.md) (or the older distutils) and called functions to declare metadata, list dependencies, and produce build artifacts. The installer had to execute `setup.py` to discover anything about the package, including which build dependencies the script itself needed. That ordering created a circular requirement: setuptools had to already be present before the installer could ask the project what it required.

The setup.py model also coupled the entire ecosystem to setuptools. Alternative build systems existed but had to wrap or mimic the setuptools entry points to work with [pip](https://pydevtools.com/handbook/reference/pip.md). Replacing setuptools meant replacing the whole installer integration, not just the build step.

## What PEP 518 added

PEP 518 introduced the [pyproject.toml](https://pydevtools.com/handbook/reference/pyproject.toml.md) file and reserved a `[build-system]` table for build configuration:

```toml
[build-system]
requires = ["setuptools>=61", "wheel"]
```

The `requires` key lists packages the frontend must install into an isolated build environment before invoking the build. Because the file is static [TOML](https://toml.io/), the frontend can read it without executing project code, and the build step starts with a known set of dependencies already in place.

[pip](https://pydevtools.com/handbook/reference/pip.md) 10.0 (April 2018) was the first installer to honor `[build-system].requires`. By that point, the file had a clear job even before any non-setuptools backends adopted it.

## What PEP 517 added

PEP 517 extended the same `[build-system]` table with a `build-backend` key and defined the functions every backend must expose:

```toml
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
```

`build-backend` names a Python object using the `module[:object]` syntax. The frontend imports that object and calls its hook functions, the most important of which are:

- `build_wheel(wheel_directory, ...)` produces a [wheel](https://pydevtools.com/handbook/reference/wheel.md) in the given directory.
- `build_sdist(sdist_directory, ...)` produces an [sdist](https://pydevtools.com/handbook/reference/sdist.md) source distribution.
- `get_requires_for_build_wheel` and `get_requires_for_build_sdist` declare any extra dependencies the backend needs, beyond the static `requires` list.
- `prepare_metadata_for_build_wheel` lets a frontend extract metadata without producing the full wheel.

[PEP 660](https://pydevtools.com/handbook/explanation/what-is-pep-660.md) later added a `build_editable` hook so backends could implement [editable installs](https://pydevtools.com/handbook/explanation/what-is-an-editable-install.md) the same way they implement regular wheels. pip 19.0 (January 2019) was the first release to support the full PEP 517 hook API; pip 21.3 (October 2021) added PEP 660 support.

## How frontends and backends fit together

PEP 517 split package building into two halves: the [build frontend](https://pydevtools.com/handbook/explanation/what-is-a-build-frontend.md) (the tool a developer invokes) and the [build backend](https://pydevtools.com/handbook/explanation/what-is-a-build-backend.md) (the library that produces artifacts).

A typical `pip install .` flow now looks like:

1. pip reads `[build-system]` from `pyproject.toml`.
2. pip creates an isolated build environment and installs the packages listed in `requires`.
3. pip imports the object named in `build-backend` and calls `build_wheel`.
4. The backend produces a wheel; pip installs it into the target environment.

Any compliant frontend can drive any compliant backend, so swapping Hatchling for flit_core, or pip for [uv](https://pydevtools.com/handbook/reference/uv.md), changes which packages handle each step without changing the contract between them.

## Map tools to each role

| Role | Tools |
| --- | --- |
| Build frontend | [pip](https://pydevtools.com/handbook/reference/pip.md), [uv](https://pydevtools.com/handbook/reference/uv.md), [build](https://pydevtools.com/handbook/reference/build.md), [Hatch](https://pydevtools.com/handbook/reference/hatch.md), [Poetry](https://pydevtools.com/handbook/reference/poetry.md), [PDM](https://pydevtools.com/handbook/reference/pdm.md) |
| Build backend | [setuptools](https://pydevtools.com/handbook/reference/setuptools.md), [Hatchling](https://pydevtools.com/handbook/reference/hatch.md), [flit_core](https://pydevtools.com/handbook/reference/flit.md), poetry-core, uv_build, maturin, scikit-build-core |

Most projects do not need to think about the distinction once it is configured. `pip install .`, `uv pip install .`, and `python -m build` all go through the same interface; the project's `[build-system]` table decides which backend gets called.

## Spell out what compatibility means

A frontend is compatible if it reads the `[build-system]` table, installs the listed `requires` into an isolated environment, and calls the hooks defined by `build-backend`. A backend is compatible if it exposes the mandatory hook functions and can be imported from a freshly installed environment. Most active Python build tools have been compatible since 2019 or earlier; the question now is rarely whether a tool supports the standards but which optional hooks (PEP 660 editable installs, in-tree build backends, build isolation toggles) it implements.

## Related

- [What is a build backend?](https://pydevtools.com/handbook/explanation/what-is-a-build-backend.md) walks through what backends actually do.
- [What is a build frontend?](https://pydevtools.com/handbook/explanation/what-is-a-build-frontend.md) covers the other side of the interface.
- [What is PEP 621 compatibility?](https://pydevtools.com/handbook/explanation/what-is-pep-621-compatibility.md) explains how project metadata moved into the same file.
- [What is PEP 660?](https://pydevtools.com/handbook/explanation/what-is-pep-660.md) standardized editable installs on top of PEP 517.
- [Should I run `python setup.py`?](https://pydevtools.com/handbook/explanation/should-i-run-python-setuppy-commands.md) covers the deprecated commands these PEPs replaced.
- [pyproject.toml reference](https://pydevtools.com/handbook/reference/pyproject.toml.md) documents every table in the file, including `[build-system]`.

## Learn More

- [PEP 517: A build-system independent format for source trees](https://peps.python.org/pep-0517/)
- [PEP 518: Specifying minimum build system requirements](https://peps.python.org/pep-0518/)
- [Python Packaging User Guide: pyproject.toml specification](https://packaging.python.org/en/latest/specifications/pyproject-toml/)
