How to Configure flake8 for Python (2026 Setup Guide)
Writing
DEVOPS & INFRASTRUCTURE
Published July 8, 202610 min read

How to Configure flake8 for Python (2026 Setup Guide)

Learn how to configure flake8 for Python: install it, set up .flake8, fix the pyproject.toml problem, wire up pre-commit, and decide if you still need it in 2026.

Rabinarayan Patra - Software Development Engineer

By Rabinarayan Patra

SDE II at Amazon

configure-flake8flake8-pyproject-tomlflake8python-lintingpre-commitdevops

Every Python project I join eventually hits the same wall. Someone adds flake8, drops the config into pyproject.toml like every other modern tool expects, runs it, and nothing happens. No error, no warning, just flake8 quietly ignoring the entire file. Then the confusion starts.

This guide walks through how to configure flake8 properly in 2026: what it actually is, where its config really lives, why pyproject.toml is the one file it refuses to read, and whether you should still reach for it now that Ruff exists. Everything here is checked against flake8 7.3.0, released June 2025.

What is flake8 and what does it actually check?

flake8 is not one linter. It is a wrapper that glues three separate tools together behind a single command, then adds a plugin system on top. When you run flake8, you are really running pyflakes, pycodestyle, and mccabe in one pass.

Each tool owns a code prefix, and knowing them makes every flake8 report readable:

  • pyflakes finds logical errors: unused imports, undefined names, unused variables. Its codes start with F.
  • pycodestyle checks PEP 8 style: whitespace, indentation, line length. It was once called pep8. Its codes start with E (errors) and W (warnings).
  • mccabe measures cyclomatic complexity. It emits exactly one code, C901, and only when you turn it on.

flake8 adds one code of its own: E999, which means the file failed to compile at all (a syntax error). So when you see F401, that is pyflakes telling you about an unused import. E501 is pycodestyle complaining about line length. C901 is mccabe saying a function is too complex.

flake8 7.3.0 pins those dependencies tightly: pyflakes 3.4.x, pycodestyle 2.14.x, and mccabe 0.7.x. It runs on Python 3.9 and up. The project lives under PyCQA (the Python Code Quality Authority) and is maintained by Anthony Sottile and Ian Cordasco.

How do you install and run flake8?

Install it with pip and point it at your code. That is the whole starting workflow:

pip install flake8
flake8 .

That runs flake8 across the current directory. You can target a specific path or module too:

flake8 src/
python -m flake8 src/

The python -m flake8 form is worth remembering. It guarantees you run the flake8 installed in the current environment, which matters the moment you have more than one Python around. On a fresh run with no config, flake8 uses its defaults: 79-character lines and a small built-in ignore list. Most teams change both, which is where config comes in.

Where does flake8 look for its configuration?

flake8 reads three config files, all in INI format under a [flake8] section: setup.cfg, tox.ini, and a dedicated .flake8 file. It does not read anything else, and it does not merge them. It picks one.

I default to a standalone .flake8 file because it keeps linter config out of files that mean other things. Here is a realistic starting point, close to what the official docs recommend:

[flake8]
max-line-length = 88
extend-ignore = E203
exclude = .git,__pycache__,docs/source/conf.py,old,build,dist
max-complexity = 10
per-file-ignores =
    __init__.py: F401

A few of those options carry sharp edges, so here is what each one actually does:

  • max-line-length defaults to 79. Setting it to 88 matches Black, which is why most projects use that number.
  • extend-ignore adds codes to the ignore list without wiping the defaults. ignore (no extend-) replaces the entire default list, which usually is not what you want. Reach for the extend- forms.
  • extend-select works the same way for turning checks on. It was added in flake8 4.0.0.
  • exclude takes comma-separated paths and globs that flake8 skips entirely.
  • per-file-ignores lets you relax rules per file. The __init__.py: F401 line above silences unused-import warnings in package init files, where re-exporting is normal.
  • max-complexity turns on the mccabe check. It is off until you set it, so C901 never fires until you add a line like max-complexity = 10.

The extend-ignore = E203 in that example is there for a reason. E203 flags whitespace before a colon, which collides with how Black formats slices. Turning it off is the standard fix for running flake8 and Black together.

Why won't flake8 read your pyproject.toml?

flake8 does not read pyproject.toml, and that is a deliberate decision, not a missing feature. This is the single most common flake8 problem I see, and the answer surprises people: the maintainer has kept the request open for years and does not intend to add it as-is.

The relevant thread is PyCQA/flake8 issue #234, and it is still open. Anthony Sottile's position is direct: "I don't think toml is the way especially with pip continuing to have weird behaviour when the file even exists." His technical objection is real. The mere presence of a pyproject.toml file changes how pip builds a package, because it can force an isolated PEP 517 build. He listed two conditions that would need to hold before he would reconsider: pip no longer changing its behavior based on the file existing, and a standard-library TOML parser. Python 3.11 added tomllib, so half of that is now true, but flake8 7.3.0 still will not read the file.

So what do you do if your project standardizes on pyproject.toml and you want flake8 config to live there too? Use a plugin.

pip install Flake8-pyproject

Flake8-pyproject lets you put your config under a [tool.flake8] section and either keeps working with the normal flake8 command or gives you a flake8p entry point:

[tool.flake8]
max-line-length = 88
extend-ignore = ["E203"]
exclude = [".git", "__pycache__", "build", "dist"]

There is also pyproject-flake8 (the pflake8 command), which monkey-patches flake8 to read the file. Both work. I lean toward Flake8-pyproject because it is the lighter touch, but understand that either one is a workaround layered on top of a tool that, by design, still does not natively support the format.

How do you silence a specific warning inline?

Add a # noqa comment to the line, and be specific about which code you are silencing. flake8 gives you two forms, and the difference matters.

A bare # noqa silences every error on that line:

import os  # noqa

A coded # noqa: E501 silences only that check. Anything else on the line still gets reported, which is exactly what you want:

url = "https://example.com/a/very/long/path/that/breaks/the/line/limit"  # noqa: E501

You can list several codes with commas, like # noqa: E731,E123, and the comment is case-insensitive. I always prefer the coded form. A bare # noqa is a blunt instrument that hides real bugs the day someone adds a second problem to that line. If you ever need to audit what your # noqa comments are actually hiding, run with --disable-noqa and flake8 reports everything as if the comments were not there.

How do you run flake8 in pre-commit and CI?

Wire flake8 into pre-commit so it runs before code ever reaches a branch. The official hook is published in the flake8 repo, and the setup is a few lines in .pre-commit-config.yaml:

-   repo: https://github.com/pycqa/flake8
    rev: 7.3.0
    hooks:
    -   id: flake8

The docs show rev: '' as a placeholder, but always pin it to a real tag like 7.3.0. An unpinned linter version means your CI can start failing on a Tuesday because a new release added a check, and nobody changed a line of code. For CI itself, there is nothing special to configure. The same flake8 . command that runs locally runs in a GitHub Actions step, reads the same config file, and exits non-zero on any violation. If you are hardening a pipeline, the same discipline I described in enabling npm trusted publishing with OIDC applies here: pin your tool versions and let the config file be the single source of truth.

Which flake8 plugins are worth adding?

Plugins extend flake8 with new checks under their own code prefixes, and installing one is just a pip install into the same environment. Most start reporting immediately, no config required. You can confirm what loaded by running flake8 --version, which lists every active plugin.

The ones I reach for most:

  • flake8-bugbear (prefix B) catches likely bugs and questionable design that pyflakes misses, like mutable default arguments.
  • flake8-comprehensions (C4) flags list and dict comprehensions that could be simpler.
  • flake8-docstrings (D) checks PEP 257 docstring conventions by wrapping pydocstyle.
  • pep8-naming (N) enforces naming conventions for classes, functions, and variables.
  • flake8-bandit adds security checks, and flake8-comprehensions and flake8-simplify clean up common code smells.

Once a plugin is installed, its codes behave like any other. Turn them on or off with select, ignore, extend-select, and extend-ignore in your config. That is the real strength of flake8: the checker set is yours to compose.

Should you still use flake8 in 2026?

Use flake8 if you depend on a plugin Ruff has not reimplemented. Otherwise, Ruff is probably the better default. That is the honest answer, and it comes straight from Ruff's own documentation rather than my opinion.

Ruff is a linter written in Rust that advertises being "10-100x faster than existing linters (like Flake8)." Its FAQ is refreshingly precise about compatibility. Ruff is a drop-in replacement for flake8 when you use it without many plugins, alongside Black, on Python 3 code. Under those conditions it "implements every rule in Flake8," including all the pyflakes F rules and a large subset of the E and W style rules.

But Ruff names its own limit clearly: "Ruff's primary limitation vis-a-vis Flake8 is that it does not support custom lint rules." That is the line that keeps flake8 alive. If your team relies on an internal flake8 plugin, or a niche community one Ruff has not ported, flake8 is still the tool that can run it. The same goes for a stable, working setup where migration churn buys you nothing. Speed is real, but a linter you have already tuned and trust has value too.

I have migrated projects to Ruff and kept others on flake8, and both calls were right for their context. The mistake is treating it as a moral question. Pick the tool that runs the checks you need, and let the benchmark difference decide only when the checks are equal.

For the details behind everything here, see the flake8 documentation, the still-open pyproject.toml discussion in issue #234, and Ruff's FAQ on flake8 compatibility.

Keep Reading

Frequently Asked Questions

What is flake8?

flake8 is a Python linter that wraps three tools behind one command: pyflakes for logical errors, pycodestyle for PEP 8 style, and mccabe for complexity. It reports issues using code prefixes (F, E, W, and C) so you can turn individual checks on or off. It is maintained by PyCQA and is one of the most widely used linters in the Python ecosystem.

Why does flake8 not read pyproject.toml?

flake8 deliberately does not support pyproject.toml. The maintainer, Anthony Sottile, has kept issue #234 open for years because the mere presence of a pyproject.toml file changes how pip builds a package. To configure flake8 with pyproject.toml you need a plugin like Flake8-pyproject, which adds a [tool.flake8] section and a flake8p command.

Should you use flake8 or Ruff in 2026?

Use Ruff if you want speed and it covers your rules, since it reimplements every flake8 rule for the common case and runs far faster. Stick with flake8 if you depend on a custom or niche flake8 plugin that Ruff has not reimplemented, because Ruff cannot run third-party flake8 plugins. Both tools are actively maintained.

How do you ignore a single flake8 error?

Add a "# noqa" comment to the line. A bare "# noqa" silences every error on that line, while "# noqa: E501" silences only the E501 check and still reports anything else. You can list multiple codes with commas, like "# noqa: E731,E123".

Rabinarayan Patra - Software Development Engineer

Rabinarayan Patra

SDE II at Amazon. Previously at ThoughtClan Technologies building systems that processed 700M+ daily transactions. I write about Java, Spring Boot, microservices, and the things I figure out along the way. More about me →

X (Twitter)LinkedIn

Stay in the loop

Get the latest articles on system design, frontend and backend development, and emerging tech trends, straight to your inbox. No spam.