requirements.txt vs pyproject.toml: What's the Difference

Why Python packaging moved from requirements.txt to pyproject.toml, what each actually does, and whether you still need a requirements.txt file

If you’ve followed an older Python tutorial, you’ve run pip install -r requirements.txt. If you’ve started a project with uv, Poetry, or Hatch recently, you have a pyproject.toml instead — and possibly no requirements.txt at all.

requirements.txt: a flat list, nothing else

A requirements.txt file is exactly what it looks like — a plain list of package names and versions, one per line:

requests==2.31.0
click>=8.0

It doesn’t distinguish between direct dependencies (what your code actually imports) and transitive ones (what those packages depend on in turn) unless you generate it carefully. It has no concept of project metadata — name, version, author, entry points — and no standard way to separate production dependencies from development-only ones like test runners, beyond maintaining a second file (requirements-dev.txt) by convention.

pyproject.toml: one file, standardized structure

PEP 621 standardized pyproject.toml as the single place for a Python project’s metadata and dependencies:

[project]
name = "my-project"
version = "0.1.0"
dependencies = ["requests>=2.31", "click>=8.0"]

[project.optional-dependencies]
dev = ["pytest>=8.0"]

One file covers project metadata, direct dependencies, and optional dependency groups (dev, test, docs) — all in a format every modern packaging tool (uv, Poetry, Hatch, pip itself) understands the same way, rather than each tool inventing its own config file.

Do you still need a requirements.txt?

Not for the dependency list itself — that’s what pyproject.toml now handles. Some teams still generate a requirements.txt as a lock-file-like export for deployment environments that expect one (certain CI systems, Docker base images, or platforms that specifically look for that filename), but it’s a build artifact at that point, not the source of truth. If you’re using uv or Poetry, your real lock file is uv.lock or poetry.lock — see our packaging tools comparison for how those differ.

Starting a new project today

Use pyproject.toml — it’s the standardized, tool-agnostic format, and every current packaging tool is built around it. There’s no reason to start a new project with a bare requirements.txt in 2026; that pattern exists mainly in older codebases that haven’t migrated yet, not as an active recommendation.

TopicsPackagingExplainerTooling