Safeguard
Developer Tools•

What does the build-system table in pyproject.toml actually do?

The build-system table tells pip and build which backend turns your source into an sdist and wheel. What requires and build-backend mean, with a safe example.

Safeguard Security Team
4 min read

The [build-system] table in pyproject.toml names the build backend and the packages needed to run it. Frontends such as pip and build read it, install those packages in an isolated environment, then call the backend to produce your sdist and wheel. Without it, your build setup is guesswork.

Definitions

A build frontend is the tool you run: python -m build, or pip when it installs from source. A build backend is the library that does the work of turning a source tree into distributions. The [build-system] table is the contract between the two.

The Python Packaging User Guide says this table should always be present, regardless of which backend you use. It has two keys that matter.

KeyWhat it holdsExample
requiresPackages needed at build time, usually just the backend["hatchling"]
build-backendThe Python object the frontend calls"hatchling.build"

A minimal, working example

[build-system]
requires = ["setuptools >= 77.0.3"]
build-backend = "setuptools.build_meta"

The guide lists matching pairs for several common backends, including hatchling, setuptools, flit-core, PDM and uv-build. Copy the pair from your backend's own documentation rather than from memory, because the module path has to be exact.

What happens when someone installs from source

When a user installs your sdist, pip reads requires, creates a temporary isolated environment, installs those packages into it, and calls the backend named in build-backend. Frontends usually build in isolation, so a missing build dependency shows up as a build error rather than silently borrowing something from the user's machine.

That isolation is also a supply chain point. Everything in requires runs code on your machine and on every machine that builds your sdist. Treat that list like any other dependency list: keep it short, and constrain versions with intent.

Common mistakes

  • Leaving the table out. Tools then fall back to legacy behaviour. State your backend explicitly.
  • Forgetting a build-time plugin. If your backend needs a plugin to compute the version from Git, it belongs in requires.
  • Loose pins on the backend. A lower bound such as >= 77.0.3 is normal. An unbounded requires means a future backend release can change how your package builds.
  • Confusing runtime and build dependencies. requires here is for building. Runtime dependencies go under [project].

What about setup.py?

The packaging guide states that setup.py and setuptools are not deprecated, but running python setup.py as a command is. Use python -m pip install . to install and python -m build to build, with setuptools as the backend if you like.

Check it yourself

Run python -m build in a clean virtual environment. If the build succeeds, both artifacts appear under dist, a source tarball and a wheel. If it fails with a missing module, the cause is almost always something absent from requires.

Safeguard's SBOM and dependency scanning can list what your library declares at runtime. Build requirements are worth reviewing separately, since they run during installs from source. You can try a free scan.

Quick check

Is the build-system table required?

The packaging guide says it is strongly recommended and should always be present. A source tree with no build configuration at all is treated as legacy and handled differently by tools.

Does requires list my runtime dependencies?

No. It lists what is needed to build the package. Runtime dependencies go in the dependencies field of the project table.

Why do I need to name a backend at all?

pip and build do not convert source to distributions themselves. A backend does that job, and the table tells the frontend which one to call.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.