Tidying INI Config Files: A Practical Guide to the INI Formatter
How to clean up INI config files with section grouping, aligned key=value pairs, alphabetical sorting, and preserved comments, plus where INI still shows up.
Tidying INI Config Files Without Breaking Them
INI is the config format nobody chose and everybody inherited. You do not write a new php.ini from scratch — you open one that has been edited by eleven people over six years, with keys indented at random, comments half-deleted, and three blank lines where someone hit Enter and walked away. The format itself is dead simple: a [section] header groups a run of key = value pairs, and a line starting with ; or # is a comment. That simplicity is exactly why it drifts into a mess. There is no compiler yelling at you for sloppy whitespace, so the sloppiness accumulates.
This guide walks through what "tidying" an INI file actually means, and how a formatter handles the parts you would otherwise do by hand.
The structure: sections, pairs, and comments
An INI file has three kinds of meaningful line. A section header in square brackets, like [mysqld], opens a group. Every key = value line after it belongs to that group until the next header appears. Anything above the first header lives in the implicit root section. Comments start with ; (the original Windows convention) or # (which arrived from Unix-style configs and systemd units), and both behave identically — they are ignored by the parser but, crucially, they carry intent. Delete them and the next person has no idea why swappiness is pinned to 10.
So the structure a formatter has to respect is: section headers group pairs, pairs hold the data, and comments annotate both. A good formatter touches the layout and leaves the meaning alone.
What "format" should and should not change
Here is the rule that matters most: formatting is whitespace, not logic. When you format an INI file, the key order inside each section should stay put unless you explicitly ask for sorting, and no value should change. That is what keeps the resulting diff honest — a reviewer sees aligned columns and consistent spacing, not a reshuffle they have to read line by line.
A formatter does three concrete things. It normalizes spacing around the = so every pair reads the same way. With Align equals turned on, it lines up the = column inside each section so the keys read like a table. With Sort sections turned on, it reorders the section headers alphabetically while keeping the implicit root section first. And through all of that, it preserves your ; and # comments in place — a full-line comment above a section stays above that section, and a trailing comment like port = 5432 ; default stays on the same line, pushed out past the aligned value so the comment column itself stays readable.
A worked example
Here is a [database] block as you might actually find it — inconsistent spacing, a stray indent, and a trailing comment crammed against its value:
[database]
host=localhost
port = 5432 ;default
max_connections= 100
user = app
# pool tuning below
idle_timeout=30
Run it through the formatter with Align equals on and Sort sections off (key order preserved), and it comes out aligned, evenly spaced, with the comment intact:
[database]
host = localhost
port = 5432 ; default
max_connections = 100
user = app
# pool tuning below
idle_timeout = 30
The = signs form a clean column, the trailing ; default is pushed clear of the value, the full-line # pool tuning below stays exactly where it was between keys, and nothing about the actual configuration changed. That is the whole point: you can scan it in one downward glance, and git blame will show this as a whitespace commit, not a logic one.
Where INI still lives
People keep predicting INI's retirement and it keeps not happening, because it is already embedded in places nobody is rewriting. php.ini configures every PHP install on earth. Git's own config — .gitconfig and per-repo config — is INI-shaped (with quoted subsections like [remote "origin"]). Linux desktop entries, the .desktop files that put icons in your application menu, are INI. So are systemd unit files: [Unit], [Service], [Install] are just sections, and ExecStart= is just a key. MySQL's my.cnf, Python's setup.cfg, and most CI runners that accept override files all speak some dialect of it.
That ubiquity is the format's only real argument. For a brand-new config in a project you fully control, TOML or YAML give you richer types and a single canonical spec — and if you are formatting those instead, the same site has a YAML formatter and a JSON formatter. But the moment you inherit one of the INI files above, type-aware tooling is beside the point; you just need it readable.
How I actually use it
I keep the INI Formatter open in a tab whenever I touch infrastructure config. The last time it earned its keep was a my.cnf review where someone had quietly added a second max_connections inside [mysqld]. MySQL takes the last duplicate and silently drops the first, so the value I thought was live was not the one the server loaded. I pasted the file, the validator flagged the duplicate with its line number, and I deleted the stale one instead of guessing. I do the same before any PR that touches a php.ini — Align equals on, no sorting, format, commit. The reviewer gets a clean column and a diff that is honestly just whitespace, which is exactly the kind of change you want to be boring.
One caution from the example above: alignment is cosmetic. Lining up the = signs makes a file pleasant to read, but it will never merge or warn you about two timeout keys. Catching duplicates is the validator's job, not the formatter's, so run both. And if you are working on a .gitconfig, leave sorting off — subsections sort by their full literal header string, not by parent then child, so [remote "origin"] can land nowhere near [remote "fork"].
Tidy config is not glamorous work. But a config file you can read in one pass is a config file you are far less likely to break at 2am, and that is worth the ten seconds it takes to paste and click Format.
Made by Toolora · Updated 2026-06-13