Skip to content

How to Configure Ruff Linter and Formatter in pyproject.toml

I just installed Ruff in my Python project and wanted to customize its behavior. The defaults are sensible, but my project has specific coding standards that differ from Ruff’s out-of-the-box configuration. Here’s what I learned about configuring Ruff through pyproject.toml.

The Problem: Default Settings Don’t Match My Project

Ruff’s defaults work great for new projects - it uses 88 character line length (same as Black), applies a core set of linting rules, and doesn’t enforce docstring conventions. But my existing codebase uses 79 character lines, requires comprehensive docstrings, and has specific style requirements.

I needed to configure Ruff to match these standards instead of fighting against the defaults.

Understanding Ruff’s Configuration Discovery

Ruff searches for configuration files in a specific order:

  1. ruff.toml in the current directory or parent directories
  2. .ruff.toml in the current directory or parent directories
  3. pyproject.toml with a [tool.ruff] section

I chose pyproject.toml because it keeps all my project configuration in one place. Ruff automatically finds and uses this file.

Basic Configuration: Line Length

My first attempt was simple - I just wanted to change the line length from the default 88 to 79 characters:

pyproject.toml
[tool.ruff]
line-length = 79

This worked immediately. Ruff now enforces 79 character lines for both formatting and the line-too-long rule (E501).

Enabling Additional Rules

Ruff has over 800 built-in rules across 50+ plugins, but only applies a core set by default. I wanted to add more rules without losing the defaults.

Here was my first mistake:

pyproject.toml
# WRONG: This disables all default rules!
[tool.ruff.lint]
select = ["UP"] # Only pyupgrade rules enabled

I used select instead of extend-select. The select option completely replaces the default rules, while extend-select adds to them. After fixing this:

pyproject.toml
# CORRECT: Extends default rules
[tool.ruff.lint]
extend-select = ["UP"] # Adds pyupgrade rules to defaults

Now I get both the default rules and the pyupgrade suggestions.

Adding Docstring Rules

My project requires comprehensive docstrings using Google style. I added the pydocstyle rules:

pyproject.toml
[tool.ruff.lint]
extend-select = [
"UP", # pyupgrade
"D", # pydocstyle
]
[tool.ruff.lint.pydocstyle]
convention = "google"

This enables all docstring checks (the “D” prefix) and configures them to expect Google-style docstrings. Ruff now catches missing docstrings, malformed docstrings, and style inconsistencies.

Python Version-Specific Rules

Some Ruff rules only apply to specific Python versions. I needed to tell Ruff which Python version my project targets:

pyproject.toml
[project]
requires-python = ">=3.10"
[tool.ruff]
line-length = 79
[tool.ruff.lint]
extend-select = [
"UP", # pyupgrade
"D", # pydocstyle
]
[tool.ruff.lint.pydocstyle]
convention = "google"

With requires-python = ">=3.10", Ruff applies version-specific rules correctly. For example, it won’t suggest Python 3.10+ syntax upgrades if my minimum version is 3.10.

Why This Matters for My Workflow

Configuring Ruff in pyproject.toml provides several benefits:

Team Consistency - Everyone on the team gets the same linting rules automatically. No need for each developer to configure their IDE separately.

Project-Specific Error Detection - I can enable rules that catch errors specific to my project’s needs, like comprehensive docstring checks.

Migration-Friendly - When migrating from other linters, I can configure Ruff to match existing conventions rather than reformatting everything.

Monorepo Support - Ruff supports hierarchical configuration. I can have a root pyproject.toml with shared settings and package-specific overrides in subdirectories.

Common Mistakes I Made

Using select instead of extend-select - This replaced all default rules instead of adding to them. I lost important checks like unused imports and undefined variables.

Forgetting requires-python - Without this, Ruff couldn’t apply version-specific rules correctly. It would suggest syntax that my minimum version doesn’t support.

Overlapping Formatter Rules - Some stylistic rules (like line length) overlap with Ruff’s formatter. By default, Ruff handles this gracefully, but I needed to understand which rules to keep in lint vs. formatter.

Ruff uses prefixes to organize its 800+ rules:

  • A: flake8-builtins (shadowing built-in names)
  • ARG: flake8-unused-arguments
  • B: flake8-bugbear (common bugs)
  • C4: flake8-comprehensions
  • D: pydocstyle (docstring conventions)
  • E: pycodestyle errors
  • F: pyflakes
  • I: isort (import sorting)
  • N: pep8-naming
  • UP: pyupgrade (modernize syntax)
  • W: pycodestyle warnings

Each prefix represents a plugin or rule category. You can enable entire categories or individual rules like E501 for line-too-long.

The Complete Configuration

Here’s the final pyproject.toml configuration I use:

pyproject.toml
[project]
requires-python = ">=3.10"
[tool.ruff]
line-length = 79
[tool.ruff.lint]
extend-select = [
"UP", # pyupgrade - modernize syntax
"D", # pydocstyle - docstring conventions
"I", # isort - import sorting
"B", # flake8-bugbear - common bugs
]
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.isort]
known-first-party = ["myproject"]

This gives me line length at 79 characters, default rules plus pyupgrade, docstring checks, import sorting, and bugbear rules, all with Google-style docstrings.

Alternative: Separate Configuration File

If you prefer keeping Ruff configuration separate from pyproject.toml, you can create a ruff.toml file:

ruff.toml
line-length = 79
[lint]
extend-select = ["UP", "D", "I", "B"]
[lint.pydocstyle]
convention = "google"

The syntax is the same, just without the [tool.ruff] prefix. Ruff will use whichever configuration file it finds first.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments