VVersions.dev

Migrate setup.py to pyproject.toml

Modern Python packaging is declarative in pyproject.toml. Move project metadata into the [project] table, declare a build backend under [build-system], and keep installs working with pip install -e .

Pattern modernizationDifficulty: moderateEffort: 2 hours to 1 daylow risk

Last verified · Updated May 22, 2026

PEP 517/518/621 made pyproject.toml the standard for Python packaging. Declare your build backend under [build-system], move metadata into the [project] table, and verify with an editable install — no executable setup.py required.

Transformation rules

setup.pypyproject.toml
setup(name=, version=)[project] name / version
install_requires[project] dependencies
extras_require[project.optional-dependencies]
entry_points[project.scripts] / [project.entry-points]
setup_requires / build deps[build-system] requires
Minimal pyproject.toml with the setuptools backend
[build-system]
requires = ["setuptools>=61"]
build-backend = "setuptools.build_meta"

[project]
name = "example"
version = "1.0.0"
requires-python = ">=3.12"
dependencies = ["requests>=2.31"]

[project.scripts]
example = "example.cli:main"

Choosing a build backend

  • setuptools — closest to legacy setup.py; lowest-friction migration.
  • hatchling — modern, fast, sensible defaults for new projects.
  • poetry-core — if you already manage dependencies with Poetry.

When the pattern applies

  • Any project still shipping an executable setup.py for metadata.
  • Packages adopting PEP 621 metadata or a new build backend.

Verification

Editable install plus a wheel/sdist build
# Verify the declarative build works$ python -m pip install -e .$ python -m build

Edge cases

  • Dynamic metadata (e.g. version from VCS) needs [tool] backend config or dynamic = [...].
  • C extensions still require a build backend that compiles them (keep setuptools).
  • Custom setup.py logic should move to backend hooks, not be ported verbatim.

Official sources