Configuration

Configuration via file

The tool.pyproject-fmt table is used when present in the pyproject.toml file:

[tool.pyproject-fmt]

# After how many columns split arrays/dicts into multiple lines and wrap long strings;
# use a trailing comma in arrays to force multiline format instead of lowering this value
column_width = 120

# Number of spaces for indentation
indent = 2

# Keep full version numbers (e.g., 1.0.0 instead of 1.0) in dependency specifiers
keep_full_version = false

# Automatically generate Python version classifiers based on requires-python
# Set to false to disable automatic classifier generation
generate_python_version_classifiers = true

# Maximum Python version for generating version classifiers
max_supported_python = "3.14"

# Table format: "short" collapses sub-tables to dotted keys, "long" expands to [table.subtable] headers
table_format = "short"

# Extra newlines between sub-tables in the same group (e.g. "\n" for one blank line between sub-tables)
sub_table_spacing = ""

# Extra newlines between root table groups (e.g. "\n" for one blank line, "\n\n" for two)
separate_root_table = "\n"

# List of tables to force expand regardless of table_format setting
expand_tables = []

# List of tables to force collapse regardless of table_format or expand_tables settings
collapse_tables = []

# List of key patterns to skip string wrapping (supports wildcards like *.parse or tool.bumpversion.*)
skip_wrap_for_keys = []

If not set they will default to values from the CLI.

Shared configuration file

You can place formatting settings in a standalone pyproject-fmt.toml file instead of (or in addition to) the [tool.pyproject-fmt] table. This is useful for monorepos or when you want to share the same configuration across multiple projects without duplicating it in each pyproject.toml.

The formatter searches for pyproject-fmt.toml starting from the directory of the file being formatted and walking up to the filesystem root. The first match wins. You can also pass an explicit path via --config:

pyproject-fmt --config /path/to/pyproject-fmt.toml pyproject.toml

The shared config file uses the same keys as the [tool.pyproject-fmt] table, but without the table header:

column_width = 120
indent = 2
table_format = "short"
sub_table_spacing = ""
separate_root_table = "\n"
max_supported_python = "3.14"

When both a shared config file and a [tool.pyproject-fmt] table exist, per-file settings from the [tool.pyproject-fmt] table take precedence over the shared config file.

Command line interface

pyproject-fmt [-h] [-V] [-s | --check] [-n] [--config path] [--column-width count]
              [--indent count] [--table-format {short,long}]
              [--sub-table-spacing SUB_TABLE_SPACING]
              [--separate-root-table SEPARATE_ROOT_TABLE] [--expand-tables EXPAND_TABLES]
              [--collapse-tables COLLAPSE_TABLES] [--skip-wrap-for-keys SKIP_WRAP_FOR_KEYS]
              [--keep-full-version] [--no-generate-python-version-classifiers]
              [--max-supported-python minor.major]
              inputs [inputs ...]

pyproject-fmt positional arguments

  • inputs - pyproject.toml file(s) to format, use '-' to read from stdin

pyproject-fmt options

  • -h, --help - show this help message and exit

  • -V, --version - print package version of pyproject_fmt

  • --config PATH - path to a shared pyproject-fmt.toml config file

pyproject-fmt run mode

  • -s, --stdout - print the formatted TOML to the stdout, implied if reading from stdin

  • --check - check and fail if any input would be formatted, printing any diffs

  • -n, --no-print-diff - Flag indicating to print diff for the check mode

pyproject-fmt formatting behavior

  • --column-width COUNT - max column width in the TOML file (default: 120)

  • --indent COUNT - number of spaces to use for indentation (default: 2)

  • --table-format TABLE_FORMAT - table format: 'short' collapses sub-tables, 'long' expands to [table.subtable] (default: short)

  • --sub-table-spacing SUB_TABLE_SPACING - extra newlines between sub-tables in the same group (e.g. ' for compact, 'n’ for one blank line) (default: )

  • --separate-root-table SEPARATE_ROOT_TABLE - extra newlines between root table groups (e.g. '\n' for one blank line, '\n\n' for two) (default: )

  • --expand-tables EXPAND_TABLES - comma-separated list of tables to force expand (default: [])

  • --collapse-tables COLLAPSE_TABLES - comma-separated list of tables to force collapse (default: [])

  • --skip-wrap-for-keys SKIP_WRAP_FOR_KEYS - comma-separated list of key patterns to skip string wrapping (supports wildcards like '*.parse') (default: [])

  • --keep-full-version - keep full dependency versions - do not remove redundant .0 from versions

  • --no-generate-python-version-classifiers - do not generate Python version classifiers based on requires-python

  • --max-supported-python MINOR.MAJOR - latest Python version the project supports (e.g. 3.14) (default: (3, 14))

Python version classifiers

This tool will automatically generate the Programming Language :: Python :: 3.X classifiers for you. To do so it needs to know the range of Python interpreter versions you support:

  • The lower bound can be set via the requires-python key in the pyproject.toml configuration file (defaults to the oldest non end of line CPython at the time of the release).

  • The upper bound, by default, will assume the latest stable release of CPython at the time of the release, but can be changed via CLI flag or the config file.

Table formatting

Note

Table formatting options are available in version 2.12.0 and later.

You can control how sub-tables are formatted in your pyproject.toml file. There are two formatting styles:

Short format (collapsed) - The default behavior where sub-tables are collapsed into dotted keys. Use this for a compact representation:

[project]
name = "myproject"
urls.homepage = "https://example.com"
urls.repository = "https://github.com/example/myproject"
scripts.mycli = "mypackage:main"

Long format (expanded) - Sub-tables are expanded into separate [table.subtable] sections. Use this for readability when tables have many keys or complex values:

[project]
name = "myproject"

[project.urls]
homepage = "https://example.com"
repository = "https://github.com/example/myproject"

[project.scripts]
mycli = "mypackage:main"

Table spacing

The sub_table_spacing and separate_root_table options control the blank lines inserted between tables. Each option takes a string of \n characters where each \n adds one blank line:

  • sub_table_spacing (default "") controls spacing between sub-tables within the same group. For example, between [tool.ruff] and [tool.ruff.lint]. Set to "\n" to add a blank line between sub-tables.

  • separate_root_table (default "\n") controls spacing between different root table groups. For example, between [project] and [tool.ruff].

[tool.pyproject-fmt]
sub_table_spacing = "\n"  # Add blank line between sub-tables
separate_root_table = "\n"  # One blank line between root table groups (default)

Configuration priority

The formatting behavior is determined by a priority system that allows you to set a global default while overriding specific tables:

  1. collapse_tables - Highest priority, forces specific tables to be collapsed regardless of other settings

  2. expand_tables - Medium priority, forces specific tables to be expanded

  3. table_format - Lowest priority, sets the default behavior for all tables not explicitly configured

This three-tier approach lets you fine-tune formatting for specific tables while maintaining a consistent default. For example:

[tool.pyproject-fmt]
table_format = "short"  # Collapse most tables
expand_tables = ["project.entry-points"]  # But expand entry-points

Specificity rules

Table selectors follow CSS-like specificity rules: more specific selectors win over less specific ones. When determining whether to collapse or expand a table, the formatter checks from most specific to least specific until it finds a match.

For example, with this configuration:

[tool.pyproject-fmt]
table_format = "long"  # Expand all tables by default
collapse_tables = ["project"]  # Collapse project sub-tables
expand_tables = ["project.optional-dependencies"]  # But expand this specific one

The behavior will be:

  • project.urls → collapsed (matches project in collapse_tables)

  • project.scripts → collapsed (matches project in collapse_tables)

  • project.optional-dependencies → expanded (matches exactly in expand_tables, more specific than project)

  • tool.ruff.lint → expanded (no match in collapse/expand, uses table_format default)

This allows you to set broad rules for parent tables while making exceptions for specific sub-tables. The specificity check walks up the table hierarchy: for project.optional-dependencies, it first checks if project.optional-dependencies is in collapse_tables or expand_tables, then checks project, then falls back to the table_format default.

Supported tables

The following sub-tables can be formatted with this configuration:

Project tables:

  • project.urls - Project URLs (homepage, repository, documentation, changelog)

  • project.scripts - Console script entry points

  • project.gui-scripts - GUI script entry points

  • project.entry-points - Custom entry point groups

  • project.optional-dependencies - Optional dependency groups

Tool tables:

  • tool.ruff.format - Ruff formatter settings

  • tool.ruff.lint - Ruff linter settings

  • Any other tool sub-tables

Array of tables:

  • project.authors - Can be inline tables or [[project.authors]]

  • project.maintainers - Can be inline tables or [[project.maintainers]]

  • Any [[table]] entries throughout the file

Array of tables ([[table]]) are automatically collapsed to inline arrays when each inline table fits within the configured column_width. For example:

# Before
[[tool.commitizen.customize.questions]]
type = "list"

[[tool.commitizen.customize.questions]]
type = "input"

# After (with table_format = "short")
[tool.commitizen]
customize.questions = [{ type = "list" }, { type = "input" }]

If any inline table exceeds column_width, the array of tables remains in [[...]] format to maintain readability and TOML 1.0.0 compatibility (inline tables cannot span multiple lines).

String wrapping

By default, the formatter wraps long strings that exceed the column width using line continuations. However, some strings such as regex patterns should not be wrapped because wrapping can break their functionality.

You can configure which keys should skip string wrapping using the skip_wrap_for_keys option:

[tool.pyproject-fmt]
skip_wrap_for_keys = ["*.parse", "*.regex", "tool.bumpversion.*"]

Pattern matching

The skip_wrap_for_keys option supports glob-like patterns:

  • Exact match: tool.bumpversion.parse matches only that specific key

  • Wildcard suffix: *.parse matches any key ending with .parse (e.g., tool.bumpversion.parse, project.parse)

  • Wildcard prefix: tool.bumpversion.* matches any key under tool.bumpversion (e.g., tool.bumpversion.parse, tool.bumpversion.serialize)

  • Global wildcard: * skips wrapping for all strings

Examples: ["*.parse", "*.regex"] to preserve regex fields, ["tool.bumpversion.*"] for a specific tool section, or ["*"] to skip all string wrapping.