pyproject-fmt: Opinionated pyproject.toml Formatter
pyproject-fmt is an opinionated formatter for pyproject.toml files. It applies packaging-aware rules that a general TOML formatter cannot: sorting dependency lists, normalizing version specifiers and package names, reordering tables into the canonical PEP 621 project metadata order, and generating Python version classifiers from requires-python. It is maintained by the tox-dev organization in the toml-fmt monorepo.
pyproject.toml. For arbitrary TOML files, use a general formatter like taplo.When to use pyproject-fmt
Use pyproject-fmt when a project’s pyproject.toml should follow one packaging-aware style without per-project debate. It takes the same opinionated stance as Ruff’s formatter or Black: a small set of options and a single canonical output, which keeps diffs small and reviews focused. It complements rather than replaces a code formatter, since it touches only the project manifest and leaves Python source untouched.
Formatting rules
pyproject-fmt rewrites pyproject.toml according to fixed rules:
- Reorders top-level tables so
[build-system]precedes[project], with[tool.*]tables after. - Reorders keys inside
[project]into the canonical PEP 621 order (name,version,description,requires-python,classifiers,dependencies, and so on). - Sorts
dependencies,optional-dependencies, and dependency groups case-insensitively. - Normalizes version specifiers by trimming redundant trailing zeros (
requests>=2.0.0becomesrequests>=2). - Normalizes distribution names to their PEP 503 canonical form (
Flaskbecomesflask). - Generates
Programming Language :: Python :: 3.xclassifiers fromrequires-python, up to the latest supported version. - Wraps arrays and long strings at the configured column width and indents with two spaces.
- Preserves comments in place.
Given this input:
[project]
name = "demo"
dependencies = ["requests>=2.0.0", "click", "Flask>=3.0.0"]
version = "0.1.0"
requires-python = ">=3.10"
description = "x"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"pyproject-fmt produces:
[build-system]
build-backend = "hatchling.build"
requires = [ "hatchling" ]
[project]
name = "demo"
version = "0.1.0"
description = "x"
requires-python = ">=3.10"
classifiers = [
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
]
dependencies = [ "click", "flask>=3", "requests>=2" ]Configuration
pyproject-fmt exposes a small set of options, settable on the command line or in a [tool.pyproject-fmt] table:
| Option | CLI flag | Default | Effect |
|---|---|---|---|
column_width |
--column-width |
120 |
Width at which arrays split across lines and strings wrap |
indent |
--indent |
2 |
Number of spaces per indentation level |
keep_full_version |
--keep-full-version |
false |
Keep redundant .0 digits instead of trimming them |
| (classifier generation) | --no-generate-python-version-classifiers |
on | Disable auto-generated Python version classifiers |
max_supported_python |
--max-supported-python |
latest stable CPython | Highest Python minor version used when generating classifiers |
A shared configuration file can be passed with --config path/to/pyproject-fmt.toml to apply the same settings across multiple projects.
Installation
# Using uv (recommended)
uv tool install pyproject-fmt
# Using pipx
pipx install pyproject-fmt
# Using pip
pip install pyproject-fmtpyproject-fmt requires Python 3.10 or newer. Installing in an isolated environment with uv or pipx avoids conflicts with project dependencies.
Usage
# Format pyproject.toml in place
pyproject-fmt pyproject.toml
# Print the formatted output instead of writing the file
pyproject-fmt --stdout pyproject.toml
# Check formatting without modifying files (useful in CI)
pyproject-fmt --check pyproject.toml
# Run once without installing
uvx pyproject-fmt pyproject.tomlThe command returns a non-zero exit code when a file would be reformatted, so --check fails CI on unformatted input.
Pre-commit hook
repos:
- repo: https://github.com/tox-dev/pyproject-fmt
rev: v2.23.0
hooks:
- id: pyproject-fmtFor setup details, see How to set up pre-commit hooks for a Python project.
Pros
- Packaging-aware: sorts dependencies and generates classifiers, which a general TOML formatter cannot do
- Opinionated and low-configuration, producing consistent diffs across projects
- Preserves comments
- Runs as a CLI, a Python module, or a pre-commit hook
Cons
- Formats only
pyproject.toml, not other TOML files - Automatic classifier generation and key reordering can produce large diffs on first run
- The opinionated output is intentional but not configurable to a house style beyond the few exposed options