Auto-formatting is supposed to end style arguments. In a lot of repositories it starts new ones: diffs full of reflowed lines nobody touched, a linter undoing what the formatter just did, and a blame view that points at one commit from 2024 for every line in the file. None of that is the formatter being bad at formatting. It is configuration and process, and all of it is fixable.
If the formatter config lives in each developer's editor settings, two people with different print widths will reformat the same file back and forth forever, and every pull request will be 80 percent noise. The config belongs in the repository: .prettierrc, a [tool.ruff] or [tool.black] block in pyproject.toml, plus .editorconfig for the editor-level basics.
Then pin the version. Formatters change their output between major releases: Prettier 3 changed the default trailing-comma behaviour, and a repo where one machine runs 2.x and CI runs 3.x will never be green and clean at the same time. Put the exact version in devDependencies or a lock file and let CI use the same one.
Classic symptom: you save, the file gets formatted, then the lint autofix changes it back, and the file is now marked dirty on every open. The cause is stylistic rules living in both tools. In JavaScript, add eslint-config-prettier last in your config so it switches off every ESLint rule that could conflict; ESLint's core formatting rules are deprecated in favour of the separate stylistic plugin, and running that plugin alongside Prettier recreates the exact problem. In Python, if you use ruff format, disable the line-length and whitespace lint rules (E501 and the E1xx family) rather than letting both have an opinion.
The principle is simple: exactly one tool owns whitespace. The linter owns correctness. Any overlap is a bug in your setup.
Introducing a formatter to an existing codebase means one enormous commit. Done carelessly, every git blame from then on points at it, and the archaeology you need during an incident is gone. Two rules. First, the reformat commit touches nothing else, no renames, no behaviour changes. Second, record it so tooling can skip it:
# .git-blame-ignore-revs
# repo-wide prettier run, no behaviour change
a1b2c3d4e5f60718293a4b5c6d7e8f9012345678
$ git config blame.ignoreRevsFile .git-blame-ignore-revs
The major hosting platforms honour that file in their blame views, but each developer still needs the local git config line, so put it in your setup script.
Formatters happily reflow vendored libraries, generated API clients, minified bundles and test snapshots, producing gigantic diffs and occasionally breaking the generator on the next run. Maintain an ignore file from day one: .prettierignore covering dist/, vendor/, generated clients and snapshot directories. For one-off exceptions inside a file that must keep its hand-aligned shape, such as an ASCII table or a matrix of test data, use // prettier-ignore or Black's # fmt: off and # fmt: on. Also watch embedded languages: a formatter that reflows a tagged template containing SQL or GraphQL can produce valid-looking code that no longer parses at the other end.
A formatter should be a fixed point: format twice, get the same bytes. When it is not, usually because of a plugin or a comment placed somewhere awkward, CI thrashes. Test it directly by formatting the repo twice in a scratch job and diffing. On print width, 80 columns and modern framework code fight constantly, since a single typed function signature can exceed it; 100 is a pragmatic default, and the argument matters far less than picking one and never revisiting it. Language-specific things to expect: Black's magic trailing comma is a deliberate hint, a trailing comma you add forces the collection to explode one-per-line, which is a feature once you know it. Prettier reformats Markdown tables and will realign the pipes, which is fine unless you also generate those tables. And gofmt has no options at all by design, which is precisely why Go teams never have this conversation.
CI must never rewrite code; it should fail and tell the developer to run the formatter. Use prettier --check ., black --check --diff . or ruff format --check, all of which exit non-zero on a difference. Go has a trap here: gofmt -l . lists unformatted files but still exits 0, so a naive CI step passes forever. Write it as test -z "$(gofmt -l .)" instead. Locally, run the formatter on save and on staged files in a pre-commit hook, so the CI check effectively never fires. For a comparison of the formatters themselves, see the code formatters ranking.