How EditorConfig Ends the Tabs-vs-Spaces War on Your Team
A practical guide to the .editorconfig file: root=true, indent style and size, end-of-line, charset, and per-filetype overrides that every editor applies automatically.
How EditorConfig Ends the Tabs-vs-Spaces War on Your Team
Every team has had the argument. One person's editor writes tabs, another's writes four spaces, and a third quietly trims trailing whitespace on save. Nobody agreed to any of this; their editors just shipped with defaults. The result is pull requests where the real change is three lines and the diff is forty, because everything around it shifted by a column. Reviewers scroll past phantom diffs, git blame points at whoever last touched the indentation rather than the logic, and merge conflicts appear in code nobody meaningfully edited.
The fix is a single file at the root of your repository named .editorconfig. It is plain text, it has no build step, and once a developer's editor has the plugin, it configures their indentation, charset, and line endings the moment they open a file. This post walks through what goes in that file, why each line matters, and how a few sections end the whitespace skirmishes for good.
What the file actually controls
EditorConfig sits one layer below formatters. Prettier and ESLint rewrite or flag your code on save or in CI, and they only run where they're installed. EditorConfig works earlier and more broadly: it tells the editor itself how to behave before you've typed a character. The properties you'll use most:
indent_style—spaceortab. This is the one that ends the war.indent_size— how many spaces per level (with tabs, it only sets display width).end_of_line—lf,crlf, orcr. Picklfand CRLF noise from Windows contributors disappears.charset— almost alwaysutf-8.insert_final_newline—trueso files end cleanly and POSIX tools stop complaining.trim_trailing_whitespace—truefor code, but you'll want it off for Markdown.
A handful of these, set once, covers the overwhelming majority of the friction a mixed-editor team generates.
The two rules that make the file behave
Before the properties, two structural pieces decide how the file is read.
The first is root = true at the very top. When an editor opens a file, EditorConfig searches that file's directory and every parent directory upward, merging every .editorconfig it finds along the way. That's powerful, but it means a stray config in your home folder or a monorepo parent can leak settings into your project. Putting root = true at the top of your project's top-level file stops the upward walk, so your repo's rules are the final word.
The second is section order. Each [glob] section applies to files matching that pattern, and EditorConfig reads sections top to bottom — later matching sections override earlier ones for the keys they set. So the broad [*] section goes first to establish defaults, and narrow globs like [*.md] go below it to override specific cases. A property a later section doesn't mention simply keeps the value from above. Flip that order and your narrow override gets clobbered by the global block. A good generator always emits [*] first for exactly this reason.
A worked example
Here is a complete, real .editorconfig with a global block and a per-filetype override. This is roughly what most repositories should start from:
# Stop the upward search at the project root
root = true
# Defaults for every file in the repo
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
insert_final_newline = true
trim_trailing_whitespace = true
# Python wants four spaces
[*.py]
indent_size = 4
# Markdown uses two trailing spaces for hard line breaks
[*.md]
trim_trailing_whitespace = false
# Makefiles break unless recipe lines start with a literal tab
[Makefile]
indent_style = tab
Read it the way the editor does. Every file inherits the [*] block: two-space indentation, LF line endings, UTF-8, a final newline, and trimmed trailing whitespace. A .py file then picks up indent_size = 4 while keeping everything else from [*]. A .md file keeps two-space indentation but turns trimming off, so a hard line break written as two trailing spaces survives. And a Makefile switches to real tabs, because GNU Make refuses to run a recipe line that starts with spaces and fails with the famously cryptic "missing separator" error. Each override is one or two lines, and each one closes a specific, recurring source of pain.
The Makefile trap is worth its own paragraph
That last section is the one people forget. If your global config forces spaces and you don't override it for Makefiles, every Makefile you touch gets silently corrupted — the editor inserts spaces where Make demands a tab, and nothing complains until you actually run make and it explodes. The failure is invisible right up until it isn't. Adding [Makefile] and [*.mk] sections with indent_style = tab makes the editor do the right thing automatically, while the rest of your repo stays on spaces.
What I do on every repo now
I used to treat .editorconfig as optional polish, something to add "later." Then I spent an afternoon untangling a pull request where a Windows teammate had rewritten an entire file's line endings to CRLF without realizing it — every single line showed as changed, and the one logic fix I needed to review was buried somewhere in the middle. After that I started committing a .editorconfig as the first file in any new project, before the first line of real code. The whole thing takes under a minute, and it has quietly prevented that exact class of review noise ever since. The honest payoff isn't elegance; it's the arguments that simply never happen because the editor already agreed with the repo.
One caveat worth knowing: indent_size = 2 with indent_style = tab does not give you two-space indentation. With tabs, indent_size only controls how wide a tab is displayed — it inserts no spaces. If you genuinely want two-space indents, you must set indent_style = space as well. It's the most common silent mistake in these files.
Build yours and commit it
You can write .editorconfig by hand from the example above, but it's faster to start from a preset and tweak. The .editorconfig Generator gives you a visual builder: toggle root = true, set the global section, add per-glob overrides like the Markdown and Makefile ones above with a click, and copy the exact file your editor will read. It runs entirely in the browser, so nothing about your project's layout leaves your machine. Pick the language preset that matches your stack, confirm the indentation, and paste the result into a file named .editorconfig at your repo root.
While you're setting up the boilerplate that travels with a repository, it pairs naturally with a .gitignore Generator — between the two, a fresh clone configures both the editor and what Git tracks, with zero instructions for the next contributor to read. Generate the file once, commit it, and the indentation, charset, and line-ending policy follow your code to every machine that ever opens it.
Made by Toolora · Updated 2026-06-13