EditorConfig Deep Dive: Syntax, Properties, and Best Practices

Every shared repository eventually rediscovers the same avoidable argument: tabs or spaces, LF or CRLF, trim trailing whitespace or leave it alone, final newline or not. None of these questions are hard. They are just repetitive, noisy, and surprisingly good at wasting review time.
EditorConfig exists to move those decisions out of personal editor settings and into the repository itself. It is a simple, cross-editor standard for defining basic text file formatting rules so contributors using different editors and IDEs still save files in a consistent way.
This matters more than style bikeshedding. Inconsistent indentation creates ugly diffs. Inconsistent line endings break shell scripts and CI jobs. Trailing whitespace produces review noise that hides the change you actually care about. EditorConfig is not glamorous, but it solves a class of problems that should stay boring.
This guide covers how EditorConfig works, the syntax of .editorconfig files, the standard properties you can rely on, where editor support stands, and how to build a practical starter template for a modern repository.
What EditorConfig Is¶
At its core, EditorConfig is two things:
- A file format for declaring editor behavior.
- A set of editor integrations that read those declarations and apply them when a file is opened or saved.
The file is always named .editorconfig. You commit it to the repository, usually at the root. Editors that support EditorConfig then use it to answer questions like:
- Should this file use tabs or spaces?
- If spaces, how many?
- What line ending should be written on save?
- Should trailing whitespace be removed?
- Should the file end with a newline?
EditorConfig is intentionally narrow. It does not replace a formatter like Prettier, Black, or gofmt, and it does not replace a linter like ESLint, Ruff, or ShellCheck. It handles the foundational text-formatting rules that are editor-shaped rather than language-shaped.
Why It Exists¶
The problem is not that teams disagree. Teams always disagree. The problem is letting those disagreements stay implicit.
Without EditorConfig, each contributor's editor falls back to local defaults. That creates several predictable failure modes:
| Problem | What It Looks Like In Practice |
|---|---|
| Tabs vs. spaces drift | Nested code lines up differently depending on who saved the file |
| Mixed line endings | A one-line change becomes a full-file diff because LF became CRLF |
| Trailing whitespace noise | Pull requests show meaningless changes on otherwise untouched lines |
| Missing final newline | Some tools complain, and diffs show \ No newline at end of file |
| Editor-specific defaults | A repo feels tidy in one IDE and messy everywhere else |
EditorConfig gives the repository a say. That is the real win.
How EditorConfig Works¶
EditorConfig resolution is straightforward once you know the search model.
The .editorconfig File¶
An EditorConfig file is plain text, stored as .editorconfig, and written in an INI-like format. It contains:
- An optional preamble at the top
- One or more sections in square brackets
- Key/value pairs inside those sections
The most important preamble setting is:
That tells EditorConfig-aware tools to stop searching parent directories for additional .editorconfig files.
Hierarchical Lookup¶
When you open a file, the EditorConfig integration starts in that file's directory and walks upward through parent directories looking for .editorconfig files.
The search stops when one of these happens:
- It reaches the filesystem root
- It finds a
.editorconfigfile whose preamble containsroot = true
This lets large repositories define broad defaults at the top level and narrower overrides in subdirectories.
For example:
In that structure:
src/app.tsis affected byrepo/.editorconfigdocs/guide.mdis affected by both files- The closer file wins when both define the same property
Order of Precedence¶
EditorConfig processing follows two simple rules:
- Files closer to the target file win over files higher in the tree.
- Within a single
.editorconfigfile, later matching sections win over earlier ones.
That means EditorConfig is both hierarchical and top-down:
- The tool reads matching files from the top of the directory tree down to the closest one
- Inside each file, it reads from top to bottom
- The most recently applied matching property wins
This makes broad defaults easy:
Here, Markdown files still inherit lf and insert_final_newline, but they override trailing-whitespace trimming.
Syntax and File Matching Deep Dive¶
EditorConfig looks simple because it is simple, but there are a few details worth getting right.
File Format Basics¶
The format is INI-like:
- Blank lines are ignored
- Comment lines start with
#or; - Section names go in square brackets
- Properties use
key = value
Example:
A Current Spec Detail: No Inline Comments¶
Modern EditorConfig specification behavior does not treat # or ; as inline comment markers when they appear later in a line. They only start comments when they are the first non-whitespace character on the line.
In other words, this is safe:
Because older parsers historically handled inline comments differently, the most portable habit is simple: put comments on their own lines.
Section Matching Rules¶
Each section header is a glob pattern that matches file paths. The pattern applies relative to the directory containing that .editorconfig file.
If the pattern contains no slash, it can match files at any depth below that directory. If it contains a slash, it becomes path-aware.
Examples:
Wildcards and Glob Patterns Reference¶
These are the standard special patterns defined by the EditorConfig specification.
| Pattern | Meaning | Example |
|---|---|---|
* | Match any string except / | [*.js] matches app.js |
** | Match any string including / | [docs/**/*.md] matches nested Markdown files |
? | Match any single character except / | [file?.txt] matches file1.txt |
[seq] | Match one character from a set | [file[ab].txt] matches filea.txt or fileb.txt |
[!seq] | Match one character not in a set | [!T]*.md excludes names starting with T |
{s1,s2,s3} | Match one of several strings | [*.{js,ts,jsx,tsx}] |
{num1..num2} | Match an integer range | [file{1..3}.txt] matches file1.txt through file3.txt |
Practical Matching Examples¶
| Pattern | Matches | Notes |
|---|---|---|
[*] | All files | Common default section |
[*.py] | Any Python file below this directory | No slash, so any depth |
[Makefile] | Files literally named Makefile | Case-sensitive in the pattern |
[docs/*.md] | Markdown files directly under docs/ | Not recursive |
[docs/**/*.md] | Markdown files at any depth under docs/ | Recursive |
[*.{json,yml,yaml}] | Multiple extensions | Cleaner than repeating sections |
[{package.json,tsconfig.json}] | Specific filenames | Useful for indentation overrides |
Removing a Previously Applied Value¶
Any property can be set to unset to remove its effect and fall back to the editor default or a lower-priority rule.
Example:
[*]
trim_trailing_whitespace = true
[*.md]
trim_trailing_whitespace = false
[docs/generated/*.md]
trim_trailing_whitespace = unset
unset is especially useful in large repositories with nested overrides.
Core Properties Reference¶
These are the properties you can count on across mainstream EditorConfig implementations. They are the heart of the format.
| Property | Allowed Values | What It Controls |
|---|---|---|
indent_style | space, tab | Whether indentation uses spaces or hard tabs |
indent_size | Integer, tab | Width of one indentation level |
tab_width | Integer | Display width of a hard tab |
end_of_line | lf, cr, crlf | Line ending format written on save |
charset | latin1, utf-8, utf-8-bom, utf-16be, utf-16le | Character encoding |
trim_trailing_whitespace | true, false | Whether spaces before newline are removed |
insert_final_newline | true, false | Whether a file should end with a newline |
Indentation Style¶
indent_style controls whether indentation uses tabs or spaces.
Allowed values:
spacetab
For most repositories, spaces are the safer shared default. If you do use tabs, define tab_width clearly so editors display them consistently.
Indentation Size¶
indent_size controls the number of columns in one indentation level.
Allowed values:
- Any whole number, such as
2,4, or8 tab
When set to tab, the indent size follows tab_width if tab_width is specified. This is useful when you want hard tabs but still want consistent visual alignment.
Tab Width:¶
tab_width controls how wide a tab character should be displayed.
Allowed values:
- Any whole number
You usually do not need this if indent_style = space. It matters when tabs are part of the repository's indentation rules or when file types require hard tabs, such as Makefiles.
End of Line¶
end_of_line controls how line endings are written:
Allowed values:
lfcrcrlf
This matters on cross-platform teams because operating systems have different historical defaults:
| Value | Meaning | Common Use |
|---|---|---|
lf | Line Feed (\n) | Linux, macOS, containers, CI |
crlf | Carriage Return + Line Feed (\r\n) | Windows-native tooling |
cr | Carriage Return (\r) | Legacy systems only |
For modern repositories, lf is the usual default. It keeps shell scripts, Docker builds, and Unix-like CI runners happy. If you need Windows-specific scripts to use CRLF, override them by file type.
Also remember the boundary: EditorConfig controls how editors save files. .gitattributes controls how Git normalizes files in the repository. For line endings, the two complement each other rather than compete. If you want a deeper Git-side treatment, see Controlling Git Repository Behavior with .gitattributes.
Character Encoding¶
charset controls the text encoding used when saving.
Allowed values:
latin1utf-8utf-8-bomutf-16beutf-16le
For almost every modern codebase, utf-8 is the right answer. utf-8-bom is still supported by the specification but generally best avoided unless a specific tool requires it.
Trim Trailing Whitespace¶
trim_trailing_whitespace controls whether whitespace at the end of lines is removed on save.
Allowed values:
truefalse
This setting is almost always worth enabling globally. The common exception is Markdown, where two trailing spaces can be used intentionally to force a hard line break.
Final Newline Insertion¶
insert_final_newline controls whether the file should end with a newline.
Allowed values:
truefalse
This is a small setting with outsized value. It keeps diffs cleaner, satisfies Unix tooling expectations, and avoids the classic \ No newline at end of file annoyance.
Editor and IDE Support¶
EditorConfig support is broad, but it is not identical everywhere. Some editors include it natively, while others still rely on an extension or plugin.
As of April 29, 2026.
The lists below reflect the support information published on editorconfig.org as checked on April 29, 2026. That landscape can change, so treat this as a dated snapshot rather than eternal truth.
Common Editors with Native Support¶
According to the EditorConfig project site, these popular tools support EditorConfig without an extra plugin:
| Editor or Platform | Notes |
|---|---|
| Visual Studio Professional | Native support |
| JetBrains IntelliJ IDEA | Listed as supported on the official site |
| JetBrains WebStorm | Native support |
| JetBrains PyCharm | Native support |
| JetBrains RubyMine | Native support |
| JetBrains Rider | Native support |
| PHPStorm | Native support |
| Vim | Native support |
| Emacs | Native support |
| Helix | Native support |
| Xcode | Native support |
| GitHub | Recognizes EditorConfig in supported editing contexts |
| GitLab | Listed as supported on the official site |
| Gitea | Listed as supported on the official site |
Common Editors That Require a Plugin or Extension¶
The official EditorConfig site lists these popular tools under plugin-based support:
| Editor | Typical Integration Path |
|---|---|
| Visual Studio Code | Install the EditorConfig extension |
| Sublime Text | Install the EditorConfig package |
| Eclipse | Install the EditorConfig plugin |
| Atom | Install the EditorConfig package |
| Notepad++ | Install the plugin |
| NetBeans | Install the plugin |
| TextMate | Install the plugin |
| VSCodium | Install an EditorConfig extension |
If your team standardizes on VS Code, it is worth calling this out in onboarding docs. A repository-level .editorconfig only helps if contributors actually have EditorConfig support enabled in the editor they use.
For related editor setup ideas, see Inside My VS Code Setup: Theme, Extensions, and Settings.
Pros and Cons¶
EditorConfig is worth using in almost every repository, but it helps to understand what it does well and what it does not try to do.
Pros¶
- Language-agnostic. The same file can define sane defaults for Python, Go, Markdown, JavaScript, shell scripts, and plain text.
- Stops basic style arguments early. Teams do not need a meeting to settle every editor default.
- Lightweight. The format is tiny, readable, and easy to review.
- Version-controlled. Rules live with the repository, which makes onboarding easier and changes auditable.
- Complements formatters and linters. It handles the low-level editor behavior that language-specific tools often assume.
- Useful even in mixed-tool shops. JetBrains, Visual Studio, Vim, Emacs, web editors, and other tools can still converge on the same basics.
Cons¶
- Limited scope. EditorConfig is not a formatter, linter, or code style engine. It will not sort imports, wrap lines intelligently, or enforce language-specific conventions.
- Support varies by editor. Some tools need plugins, and teams forget plugins more often than they forget to argue about tabs.
- Not every property is honored equally everywhere. The standard is clear, but editor integrations can differ in edge cases.
- It solves only the first layer of consistency. You still need tools like Prettier, ESLint, Black, Ruff,
gofmt, orpre-commitfor deeper enforcement.
If you want a practical way to enforce these rules before code reaches CI, pair EditorConfig with pre-commit.
Practical Example: The Ultimate Starter Template¶
The best .editorconfig is not the most clever one. It is the one your team understands at a glance and rarely needs to revisit.
This template is a strong starting point for a modern repository with JavaScript, TypeScript, Python, Markdown, YAML, JSON, shell scripts, and Makefiles.
# Stop searching above the repository root.
root = true
# Default rules for all files.
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
# JavaScript and TypeScript usually use 2 spaces.
[*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}]
indent_style = space
indent_size = 2
# JSON, YAML, and web config files are commonly 2 spaces.
[*.{json,jsonc,yml,yaml,html,css,scss}]
indent_style = space
indent_size = 2
# Python convention is 4 spaces.
[*.py]
indent_style = space
indent_size = 4
# Shell scripts are usually 2 or 4 spaces. Pick one and be explicit.
[*.{sh,bash,zsh}]
indent_style = space
indent_size = 2
# Go uses tabs for indentation in source files.
[*.go]
indent_style = tab
tab_width = 4
# Markdown often needs trailing spaces for intentional hard line breaks.
[*.md]
trim_trailing_whitespace = false
# Makefiles require real tab characters for recipe lines.
[Makefile]
indent_style = tab
tab_width = 4
# Windows command scripts may need CRLF for compatibility.
[*.{bat,cmd,ps1}]
end_of_line = crlf
# Generated or vendored content is sometimes best left alone.
[vendor/**]
indent_size = unset
trim_trailing_whitespace = unset
end_of_line = unset
Why This Template Works¶
It follows a few sane defaults:
- Global rules are simple and conservative.
- Language-specific overrides are explicit.
- Markdown gets a trailing-whitespace exception.
- Makefiles and Go get tab-aware handling.
- Windows-native scripts can opt into
CRLFwithout polluting everything else.
If your repository uses Prettier, Black, or another formatter, keep this file focused on editor behavior. Let the formatter own formatter-shaped decisions.
A Simpler Template for Small Repositories¶
If your repository is mostly prose, shell, and a little code, you may only need this:
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[*.md]
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
Use the boring tool when boring is the point.
Best Practices¶
EditorConfig pays off fastest when the file stays small, obvious, and intentional.
1. Start with One Root File¶
Most repositories should begin with a single root-level .editorconfig and only add subdirectory overrides when there is a real need.
Too many nested files make rule resolution harder to reason about.
2. Keep the Defaults Broad and the Overrides Specific¶
This pattern scales well:
- Put shared rules in
[*] - Override only the file types that genuinely differ
- Avoid repeating the same property in many sections
3. Prefer utf-8 and lf Unless a Tool Requires Otherwise¶
These are the least surprising defaults for modern development across macOS, Linux, CI runners, containers, and Git-hosted repositories.
4. Make Markdown an Explicit Exception¶
If your team writes documentation, blog posts, or release notes in Markdown, consider:
That avoids breaking intentional hard line breaks.
5. Pair It with .gitattributes¶
EditorConfig tells editors how to save files. .gitattributes tells Git how to normalize and treat files in the repository. If line endings matter to your workflow, use both.
6. Pair It with pre-commit or CI Checks¶
EditorConfig is strongest when local editor behavior and automation reinforce each other. At minimum, consider pre-commit hooks or CI checks for:
- Trailing whitespace
- Final newline
- Mixed line endings
- Formatter drift
7. Document the Expectation¶
Especially for VS Code or other plugin-based editors, tell contributors that the repository uses EditorConfig and mention any required extension in your CONTRIBUTING.md or onboarding docs.
If your repository does not already have a .editorconfig, add one. Keep it short. Start with UTF-8, LF, final newline insertion, trailing whitespace trimming, and an explicit indentation rule. Then add exceptions only where the file types justify them.
EditorConfig will not end every code-style debate on its own, and it is not meant to. What it does is remove the low-value arguments and a surprising amount of diff noise before they ever reach review. That is enough to make it one of the highest-leverage tiny files in a repository.