# Why Should I Choose pyproject.toml over requirements.txt for managing dependencies?

[requirements.txt](https://pydevtools.com/handbook/reference/requirements.md) lists packages to install. {{< term "pyproject-toml" "pyproject.toml" >}} defines a project: its {{< term "dependency" "dependencies" >}}, the Python version it requires, its metadata, and the tools that build and check it. That distinction shapes how dependencies get managed, how environments get reproduced, and how projects scale.

For new projects, use pyproject.toml. The rest of this page explains why, compares the two formats side by side, and covers the cases where requirements.txt still makes sense.

## Compare the two formats

| | requirements.txt | pyproject.toml |
|---|---|---|
| **Format** | Plain text, one package per line | TOML with standardized sections |
| **Dependencies** | Flat list | Structured `[project.dependencies]` table |
| **Python version constraint** | None | `requires-python` field |
| **Dev/test separation** | Ad-hoc files (requirements-dev.txt) | [Dependency groups](https://pydevtools.com/handbook/explanation/what-are-optional-dependencies-and-dependency-groups.md) |
| **Project metadata** | Lives elsewhere (setup.py, setup.cfg) | Built in: name, version, authors, license |
| **Tool configuration** | Separate config files per tool | `[tool.*]` tables in the same file |
| **{{< term "lockfile" "Lockfile" >}} support** | Manual pinning or pip-compile | `uv lock` / `uv sync` from the same file |
| **Reproducibility** | Depends on discipline | {{< term "lockfile" >}} tracks exact versions and hashes |
| **Standardization** | Informal convention | [PEP 621](https://pydevtools.com/handbook/explanation/what-is-pep-621-compatibility.md) specification |

## Where requirements.txt still wins

The format is plain text, one package per line:

```text
requests>=2.28.0
pandas~=2.0.0
```

Any tool that speaks pip can consume it. For a script that only needs `requests`, a one-line text file is the right tool.

## Where requirements.txt breaks down

The workflow around requirements.txt demands manual coordination. A developer creates a {{< term "virtual-environment" "virtual environment" >}}, activates it, installs packages with pip, then remembers to update the file by hand. Each step is a chance for the file and the environment to drift apart.

The format also has structural gaps:

- No Python version constraint. Nothing stops installation into an incompatible {{< term "interpreter" >}}.
- No dev/prod separation. Splitting dependencies requires multiple files (requirements-dev.txt, requirements-test.txt) with no standard convention.
- No project metadata. Name, version, authors, and build configuration live elsewhere (setup.py, setup.cfg), spreading a project's identity across files.

Tools like [pip-tools](https://pydevtools.com/handbook/reference/pip-tools.md) solve some of these problems: `pip-compile` generates pinned requirements from abstract dependencies, adding lockfile-like reproducibility. pip-tools improves the workflow without fixing the format's structural gaps: no metadata, no Python version constraints, no dev/test separation.

## How pyproject.toml consolidates the workflow

[PEP 621](https://pydevtools.com/handbook/explanation/what-is-pep-621-compatibility.md) standardized project metadata in pyproject.toml. Dependencies, Python version constraints, project metadata, and tool configuration all live in one file. Tools like [flit](https://pydevtools.com/handbook/reference/flit.md), [hatch](https://pydevtools.com/handbook/reference/hatch.md), [pdm](https://pydevtools.com/handbook/reference/pdm.md), [poetry](https://pydevtools.com/handbook/reference/poetry.md), and [uv](https://pydevtools.com/handbook/reference/uv.md) (recommended) all read it.

With [uv](https://pydevtools.com/handbook/reference/uv.md), creating a project and adding dependencies collapses to two commands:

```bash
uv init myproject && cd myproject
uv add requests pandas
```

`uv add` creates the {{< term "virtual-environment" "virtual environment" >}}, installs packages, and records them in pyproject.toml. When another developer clones the repo, `uv sync` reproduces the environment from the {{< term "lockfile" >}}.

## Replace scattered config with one file

pyproject.toml replaces the constellation of setup.py, setup.cfg, [tox](https://pydevtools.com/handbook/reference/tox.md).ini, and requirements files with a single source of truth. The same file works whether the project is an application or an installable library. {{< term "dev-dependency" "Dev dependencies" >}} and [dependency groups](https://pydevtools.com/handbook/explanation/what-are-optional-dependencies-and-dependency-groups.md) live alongside production dependencies, governed by a standard rather than ad-hoc file naming.

## Bridge the gap with `uv export`

Switching to pyproject.toml does not mean abandoning every tool that reads requirements.txt. `uv export` generates a requirements.txt from pyproject.toml and its {{< term "lockfile" >}}:

```bash
uv export --format requirements-txt > requirements.txt
```

This covers deployment targets that expect requirements.txt: Docker images, cloud functions (AWS Lambda, Google Cloud Run), and CI caches. The source of truth stays in pyproject.toml; the requirements.txt becomes a generated artifact.

> [!TIP]
> [How to use uv in a Dockerfile](https://pydevtools.com/handbook/how-to/how-to-use-uv-in-a-dockerfile.md) and [Build a production Docker image for a uv project](https://pydevtools.com/handbook/tutorial/build-a-production-docker-image-for-a-uv-project.md) show how to install directly from pyproject.toml inside Docker without generating requirements.txt at all.

## Where does setup.py fit?

setup.py was the original way to declare project metadata and build instructions. [PEP 621](https://pydevtools.com/handbook/explanation/what-is-pep-621-compatibility.md) moved that metadata into pyproject.toml, making setup.py unnecessary for new projects. Existing setup.py projects can migrate with [How to migrate from setup.py to pyproject.toml](https://pydevtools.com/handbook/how-to/migrate-setup-py.md).

## Start new projects with pyproject.toml

Use pyproject.toml for new projects. Keep requirements.txt for single-file scripts where a full project structure is unnecessary, or as a generated deployment artifact via `uv export`.

### Pick your next step

- **Starting fresh?** [Create your first Python project](https://pydevtools.com/handbook/tutorial/create-your-first-python-project.md) walks through `uv init`, adding dependencies, and running code from scratch.
- **Have a requirements.txt project?** [How to migrate from requirements.txt to pyproject.toml with uv](https://pydevtools.com/handbook/how-to/migrate-requirements.txt.md) converts it step by step.
- **Building a web app?** Set up [FastAPI](https://pydevtools.com/handbook/tutorial/set-up-a-fastapi-project-with-uv.md) or [Django](https://pydevtools.com/handbook/tutorial/set-up-a-django-project-with-uv.md) with uv and pyproject.toml.
- **Deploying to production?** [Build a production Docker image for a uv project](https://pydevtools.com/handbook/tutorial/build-a-production-docker-image-for-a-uv-project.md) covers multi-stage builds with pyproject.toml.
- **Want the full setup?** [Set up a complete Python project](https://pydevtools.com/handbook/tutorial/set-up-a-complete-python-project.md) adds linting, formatting, type checking, and testing on top of pyproject.toml.
