# pyproject.toml: The Standard Python Project Configuration File


pyproject.toml is the standard configuration file for a Python project. It is written in [TOML](https://toml.io/) and lives in the project root. A single file captures three kinds of information that older Python projects scattered across `setup.py`, `setup.cfg`, `MANIFEST.in`, `requirements.txt`, `.flake8`, `mypy.ini`, `pytest.ini`, and `tox.ini`: project metadata, build-system requirements, and third-party tool configuration.

[PEP 518](https://peps.python.org/pep-0518/) introduced pyproject.toml in 2016 to declare build-system requirements in a tool-agnostic way. [PEP 621](https://peps.python.org/pep-0621/) extended it in 2020 with a standardized `[project]` table for project metadata. Together, the two PEPs make pyproject.toml the canonical home for a Python project's configuration.

> [!TIP]
> Run `uv init` to scaffold a new project with a pyproject.toml pre-configured with modern defaults. See [How to create your first Python project](https://pydevtools.com/handbook/tutorial/create-your-first-python-project.md) for a walkthrough.

## Structure

A pyproject.toml file contains three standard top-level tables. All are optional, but most projects use at least one.

| Table | Purpose | Defined by |
|---|---|---|
| `[build-system]` | Which build backend to use and its dependencies | [PEP 518](https://peps.python.org/pep-0518/) |
| `[project]` | Project metadata: name, version, dependencies, authors | [PEP 621](https://peps.python.org/pep-0621/) |
| `[tool.<name>]` | Configuration for third-party tools (ruff, mypy, pytest, uv, etc.) | [PEP 518](https://peps.python.org/pep-0518/) (reserved namespace) |

A minimal pyproject.toml for a library looks like this:

```toml {filename="pyproject.toml"}
[project]
name = "mypackage"
version = "0.1.0"
description = "A small example package."
requires-python = ">=3.9"
dependencies = [
    "requests>=2.28",
]

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

## The `[project]` Table

The `[project]` table holds standard project metadata defined by [PEP 621](https://pydevtools.com/handbook/explanation/what-is-pep-621-compatibility.md). Any compliant build backend reads these fields; they do not depend on which tool is in use.

```toml {filename="pyproject.toml"}
[project]
name = "mypackage"
version = "1.2.3"
description = "A short summary of the project."
readme = "README.md"
requires-python = ">=3.9"
license = "MIT"
authors = [
    { name = "Jane Doe", email = "jane@example.com" },
]
keywords = ["example", "packaging"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Programming Language :: Python :: 3",
]
dependencies = [
    "requests>=2.28",
    "pandas~=2.0",
]

[project.optional-dependencies]
dev = ["pytest>=7.0", "ruff"]
docs = ["sphinx", "furo"]

[project.urls]
Homepage = "https://example.com/mypackage"
Repository = "https://github.com/example/mypackage"
Documentation = "https://mypackage.readthedocs.io"
Issues = "https://github.com/example/mypackage/issues"

[project.scripts]
mypackage = "mypackage.cli:main"
```

### Required and recommended fields

- `name` (required): the distribution name used on PyPI. Lowercased and normalized per [PEP 503](https://peps.python.org/pep-0503/) for lookups.
- `version` (required unless listed in `dynamic`): a [PEP 440](https://peps.python.org/pep-0440/)-compliant version string, or `dynamic = ["version"]` to defer to the build backend.
- `description`: a one-line summary shown on PyPI.
- `readme`: a path to the long-form description file. A string is interpreted as a filename; a table supports explicit content types and inline text.
- `requires-python`: a version specifier (for example, `">=3.9"`) that declares which Python interpreters the project supports. Installers refuse to install on incompatible interpreters.
- `license`: an [SPDX identifier](https://spdx.org/licenses/) (for example, `"MIT"`, `"Apache-2.0"`) as standardized by [PEP 639](https://peps.python.org/pep-0639/). Older pyproject.toml files use `license = { file = "LICENSE" }` or `license = { text = "MIT" }`.
- `authors` and `maintainers`: lists of `{ name, email }` tables.
- `dependencies`: runtime dependencies as [PEP 508](https://peps.python.org/pep-0508/) requirement strings. Each entry accepts version specifiers, extras, and environment markers.
- `optional-dependencies`: a table of named extras. Installing `mypackage[dev]` pulls in the `dev` list.
- `keywords` and `classifiers`: PyPI search metadata.
- `urls`: a table of labeled project links shown on PyPI.
- `scripts` and `gui-scripts`: console-script entry points. `mypackage = "mypackage.cli:main"` creates a `mypackage` command that calls `main()` in `mypackage.cli` at install time.
- `entry-points`: named entry-point groups for plugin systems.
- `dynamic`: a list of field names whose values the build backend computes at build time. Common uses include `version` (with [hatch-vcs](https://github.com/ofek/hatch-vcs), [setuptools-scm](https://github.com/pypa/setuptools-scm), or [uv-dynamic-versioning](https://pydevtools.com/handbook/how-to/how-to-add-dynamic-versioning-to-uv-projects.md) for uv projects) and `readme`.

### Dependency groups (PEP 735)

For development-only dependencies that should not ship as extras, use `[dependency-groups]` as specified by [PEP 735](https://pydevtools.com/handbook/explanation/what-is-pep-735.md):

```toml {filename="pyproject.toml"}
[dependency-groups]
dev = [
    "pytest>=7.0",
    "ruff",
    "mypy",
]
docs = [
    "sphinx",
    "furo",
]
```

Dependency groups are a top-level table, not part of `[project]`. See [What are optional dependencies and dependency groups?](https://pydevtools.com/handbook/explanation/what-are-optional-dependencies-and-dependency-groups.md) for the distinction between extras and groups, and [Understanding dependency groups in uv](https://pydevtools.com/handbook/explanation/understanding-dependency-groups-in-uv.md) for how uv installs them with `uv sync --group`.

## The `[build-system]` Table

The `[build-system]` table tells installers which build backend to use when creating a wheel or sdist from source. It is defined by [PEP 517](https://pydevtools.com/handbook/explanation/what-is-pep-517.md) and [PEP 518](https://peps.python.org/pep-0518/).

```toml {filename="pyproject.toml"}
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
```

- `requires`: packages that must be installed in an isolated environment before building the project. Usually contains the build backend itself.
- `build-backend`: the Python object that exposes the [PEP 517 build hooks](https://peps.python.org/pep-0517/#build-backend-interface) (`build_wheel`, `build_sdist`, and so on).
- `backend-path`: an optional list of directories the installer should add to `sys.path` before importing the backend. Used for in-tree backends.

Common build backends include:

| Backend | `build-backend` | Notes |
|---|---|---|
| `uv_build` | `uv_build` | Default backend for `uv init --lib` and `uv init --package`. Ships with [uv](https://pydevtools.com/handbook/reference/uv.md) and is the fastest option for pure-Python projects. |
| [Setuptools](https://pydevtools.com/handbook/reference/setuptools.md) | `setuptools.build_meta` | Default for legacy projects. Supports C extensions. |
| [Hatchling](https://pydevtools.com/handbook/reference/hatch.md) | `hatchling.build` | Default backend for [Hatch](https://pydevtools.com/handbook/reference/hatch.md); available in `uv init --build-backend hatch`. Fast and standards-focused. |
| [Flit](https://pydevtools.com/handbook/reference/flit.md) | `flit_core.buildapi` | Minimal. Pure-Python projects only. |
| [PDM](https://pydevtools.com/handbook/reference/pdm.md) | `pdm.backend` | Ships with PDM but works standalone. |
| [Poetry](https://pydevtools.com/handbook/reference/poetry.md) | `poetry.core.masonry.api` | Required for Poetry-managed projects. |
| [Maturin](https://www.maturin.rs/) | `maturin` | For projects with Rust extensions via PyO3. |
| [scikit-build-core](https://scikit-build-core.readthedocs.io/) | `scikit_build_core.build` | For CMake-based C/C++ extensions. |

See [What is a build backend?](https://pydevtools.com/handbook/explanation/what-is-a-build-backend.md) for how backends fit into the build pipeline, and [What is a build frontend?](https://pydevtools.com/handbook/explanation/what-is-a-build-frontend.md) for the frontend tools (pip, uv, build) that invoke the backend.

## The `[tool.*]` Namespace

The `[tool]` table is reserved for third-party tools. Each tool claims a sub-table under `[tool.<name>]` and defines its own schema. PEP 518 reserved the namespace but does not regulate what goes inside it; tools are free to use TOML however they like.

```toml {filename="pyproject.toml"}
[tool.ruff]
line-length = 88
target-version = "py312"

[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
ignore = ["E501"]

[tool.ruff.format]
quote-style = "double"

[tool.mypy]
strict = true
python_version = "3.12"
warn_unused_ignores = true

[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-ra --strict-markers"

[tool.uv]
package = true
```

Tools that read configuration from pyproject.toml include [Ruff](https://pydevtools.com/handbook/reference/ruff.md), [Black](https://pydevtools.com/handbook/reference/black.md), [mypy](https://pydevtools.com/handbook/reference/mypy.md), [pyright](https://pydevtools.com/handbook/reference/pyright.md), [ty](https://pydevtools.com/handbook/reference/ty.md), [pytest](https://pydevtools.com/handbook/reference/pytest.md), [Coverage.py](https://coverage.readthedocs.io/), [tox](https://pydevtools.com/handbook/reference/tox.md) (via `[tool.tox]`), [uv](https://pydevtools.com/handbook/reference/uv.md), [Poetry](https://pydevtools.com/handbook/reference/poetry.md), [Hatch](https://pydevtools.com/handbook/reference/hatch.md), and [PDM](https://pydevtools.com/handbook/reference/pdm.md).

The pytest table is named `[tool.pytest.ini_options]` (not `[tool.pytest]`) to preserve its `pytest.ini` schema inside pyproject.toml.

## pyproject.toml vs setup.py

| | pyproject.toml | setup.py |
|---|---|---|
| Format | Declarative TOML | Executable Python |
| Build input | Static metadata read by any PEP 517 backend | Arbitrary code run at build time |
| Tool configuration | Centralized under `[tool.*]` | Scattered across `setup.cfg`, `.flake8`, `mypy.ini`, etc. |
| PEP 621 metadata | Native | Not supported |
| Isolated builds | Required by PEP 517 | Optional |
| Status | Current standard | Legacy; still supported by setuptools as a fallback |

setup.py is not deprecated, but it is no longer the recommended entry point for new projects. Setuptools still supports `setup.py` for legacy use cases, and it remains necessary for custom build logic that cannot be expressed declaratively. Most projects should migrate. See [How to migrate from setup.py to pyproject.toml](https://pydevtools.com/handbook/how-to/migrate-setup-py.md) and [Should I run python setup.py commands?](https://pydevtools.com/handbook/explanation/should-i-run-python-setuppy-commands.md).

## pyproject.toml vs requirements.txt

| | pyproject.toml | requirements.txt |
|---|---|---|
| Defines a project | Yes (name, version, metadata) | No |
| Declares dependencies | Yes, in `[project.dependencies]` | Yes, as flat list |
| Separates dev from runtime | Yes, via optional-dependencies or dependency groups | No, typically split into multiple files |
| Used for deployment pinning | With a lockfile (uv.lock, [PEP 751](https://pydevtools.com/handbook/explanation/what-is-pep-751.md) pylock) | Yes, as a pinned export |
| Used by build backends | Yes | No |

The two formats solve different problems. pyproject.toml describes the project; requirements.txt describes an install. Most modern workflows declare dependencies in pyproject.toml and export a pinned requirements.txt (or lockfile) for deployment. See [How to migrate from requirements.txt to pyproject.toml with uv](https://pydevtools.com/handbook/how-to/migrate-requirements.txt.md) and [Why should I choose pyproject.toml over requirements.txt?](https://pydevtools.com/handbook/explanation/pyproject-vs-requirements.md).

## Pros

- Single source of truth for project metadata, dependencies, and tool configuration
- Standardized by PEPs 517, 518, 621, and 735; not owned by any single tool
- Declarative format prevents arbitrary code execution at build time
- Supported by every modern Python build backend and package manager
- Replaces `setup.py`, `setup.cfg`, `MANIFEST.in`, `.flake8`, `mypy.ini`, `pytest.ini`, and others

## Cons

- Some legacy build logic still requires a `setup.py` shim
- The `[tool.*]` namespace is reserved but unregulated, so each tool invents its own schema
- TOML does not support complex data structures, which limits what some tools can express
- The PEP 621 metadata spec does not cover every legacy setuptools field; `dynamic` is often needed

## Learn More

- [How to migrate from setup.py to pyproject.toml](https://pydevtools.com/handbook/how-to/migrate-setup-py.md)
- [How to migrate from requirements.txt to pyproject.toml with uv](https://pydevtools.com/handbook/how-to/migrate-requirements.txt.md)
- [Why should I choose pyproject.toml over requirements.txt?](https://pydevtools.com/handbook/explanation/pyproject-vs-requirements.md)
- [What is PEP 517 / 518 compatibility?](https://pydevtools.com/handbook/explanation/what-is-pep-517.md)
- [What is PEP 621 compatibility?](https://pydevtools.com/handbook/explanation/what-is-pep-621-compatibility.md)
- [What is PEP 735 (dependency groups)?](https://pydevtools.com/handbook/explanation/what-is-pep-735.md)
- [What is a build backend?](https://pydevtools.com/handbook/explanation/what-is-a-build-backend.md)
- [What is a build frontend?](https://pydevtools.com/handbook/explanation/what-is-a-build-frontend.md)
- [Understanding dependency groups in uv](https://pydevtools.com/handbook/explanation/understanding-dependency-groups-in-uv.md)
- [pyproject.toml specification (Python Packaging User Guide)](https://packaging.python.org/en/latest/specifications/pyproject-toml/)
- [PEP 518 — Build System Requirements](https://peps.python.org/pep-0518/)
- [PEP 621 — Storing Project Metadata in pyproject.toml](https://peps.python.org/pep-0621/)
- [TOML specification](https://toml.io/)
- [uv](https://pydevtools.com/handbook/reference/uv.md) reference
