Skip to content

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-fmt only formats 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.0 becomes requests>=2).
  • Normalizes distribution names to their PEP 503 canonical form (Flask becomes flask).
  • Generates Programming Language :: Python :: 3.x classifiers from requires-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-fmt

pyproject-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.toml

The 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-fmt

For 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

Learn More

Last updated on