Guide
Moving Python dependencies from requirements.txt to pyproject.toml
For years, Python projects listed their dependencies in requirements.txt and their metadata in setup.py. Today pyproject.toml holds both, and modern tools such as uv, Poetry, Hatch and PDM expect it. Converting by hand means retyping every line and getting extras and markers right. This converter reads a requirements file and writes the pyproject sections for it, and can turn a pyproject back into requirements for tools that still need one.
What a requirement line contains
Each line follows PEP 508. It starts with a package name, optionally followed by extras in square brackets, version specifiers and an environment marker after a semicolon: pywin32==306 ; sys_platform == "win32" installs that package only on Windows. Version specifiers come from PEP 440: == pins, >= and < set bounds, and ~= is the compatible-release operator. A direct reference looks like mypkg @ https://example.com/mypkg-1.0.tar.gz.
The PEP 621 format
The default output is the standard [project] table used by uv, pip and most build backends. Every requirement becomes a string in the dependencies array, exactly as PEP 508 writes it, with double quotes inside markers changed to single quotes so the TOML string stays valid. The table also gets name, version and requires-python, and a minimal [build-system] using hatchling so pip install . works.
Development dependencies
Test and lint tools belong in a separate group so production installs stay small. Paste them into the development box and they appear under [dependency-groups] as a dev list, the format defined by PEP 735, which uv and pip understand. In Poetry mode they go into Poetry’s own group table instead.
The Poetry format
Poetry keeps dependencies in [tool.poetry.dependencies] as a table. A plain version becomes name = "2.32.3", ranges are carried over, and a requirement with extras or markers becomes an inline table such as requests = { version = "2.32.3", extras = ["socks"] }. The Python requirement is written as a caret, so >=3.11 becomes ^3.11.
Lines that are not converted
Some lines in requirements files are instructions for pip rather than requirements. -r other.txt includes another file, -c adds constraints, --index-url changes the package index and -e installs a local project in editable mode. A Git or URL line without a package name cannot become a named dependency either. Each of these is listed in a note so nothing disappears silently, and hashes from --hash are dropped because pyproject-based tools keep hashes in their lockfiles.
A worked example
The Sample requirements file has nine lines. The output’s dependencies array contains six entries, including "Django>=4.2,<5.0", "psycopg[binary]>=3.1", "celery~=5.4" and "pywin32==306; sys_platform == 'win32'". Two notes explain the skipped lines: the -r requirements-dev.txt include, and the Git URL that has no package name. Switching to Poetry rewrites the same dependencies as [tool.poetry.dependencies].
The reverse direction
Choose “pyproject.toml → requirements.txt” and paste a pyproject file. PEP 621 dependency strings are written out as they are; Poetry entries are converted, with a caret like ^4.2 expanded to >=4.2,<5, and * written as the bare name. This is useful for Docker builds, deployment platforms and scanners that only read requirements files.
Related tools
- package.json Analyzer reviews the JavaScript equivalent of this file.
- TOML Converter checks the pyproject syntax and converts it to JSON.
- Dockerfile Generator installs the requirements in an image.
- .env Converter handles the settings that do not belong in pyproject.
Checked against the current version of the requirements.txt to pyproject.toml on .

