Format and clean up INI configuration files to make them easier to read, edit, and share—right in your browser.
desktop.ini, a php.ini, a systemd unit file, a .gitconfig, an .editorconfig, a Python ConfigParser file, or any [section]/key=value snippet. The parser handles UTF-8 input including dotted-name sections, quoted Git subsections, and backslash-continued lines.=, indents key/value lines under their [section] header, separates sections with a single blank line, and aligns inline comments. Section order and key order are preserved by default so diffs only show real changes..ini file. Everything runs in your browser session — no upload, no signup, no telemetry.The INI file format has no single official specification — it grew out of Microsoft Windows WIN.INI in the 1980s, was adopted by countless apps, and each implementation evolved its own quirks. That's exactly why a normalising formatter is useful: a file that one parser accepts may trip another over a stray comment marker, an unquoted Git subsection, or inconsistent whitespace. Formatting catches these issues before they hit a deployment.
desktop.ini, boot.ini-style files, and many old installer scripts use the classic Windows dialect: [Section] headers, key=value lines, and ; for comments. The Windows API's GetPrivateProfileString requires comments on lines by themselves, so a formatter that aligns comments to their own line keeps these files compatible.php.ini, my.cnf, and redis.conf — Long-running services bundle their config as INI-style text with hundreds of key=value directives across [mysqld], [client], or [Session]-style sections. Formatting groups directives under their headers and removes accumulated whitespace drift after months of hand edits..gitconfig and .gitmodules — Git uses an INI flavour with quoted subsections: [remote "origin"], [branch "main"], [submodule "vendor/lib"]. Subsection names are case-sensitive (unlike the [section] part). A formatter that preserves the quoted form avoids subtle bugs where [remote "Origin"] and [remote "origin"] reference different remotes..service, .timer, .socket, and .mount files use INI syntax inspired by the XDG Desktop Entry spec: [Unit], [Service], [Install] sections with Key=Value directives, no quotes around values, and line continuation via trailing backslash. Formatting these makes overrides via systemctl edit cleaner to review.configparser accepts both = and : as the key-value delimiter and both ; and # as comment markers by default. Round-tripping a file through configparser.write() then formatting it here is a quick way to check the result before committing.tox.ini, pytest.ini, setup.cfg, .editorconfig) makes git diffs unreadable. Formatting before commit means reviewers see only behavioural changes, not trailing-space churn.| Dialect | Section format | Comments | Delimiter | Line continuation | Other quirks |
|---|---|---|---|---|---|
Classic Windows (WIN.INI, desktop.ini) |
[Section] |
; only; comments must be on their own line per GetPrivateProfileString |
= only |
Not supported | Section/key names case-insensitive; values are strings |
Python configparser |
[Section] |
# or ; as full-line prefixes (configurable); inline comments off by default |
= or : |
Multi-line values via deeper indentation, not \ |
Section names case-sensitive; keys lowercased by default; flat — no real nesting |
Git config (.gitconfig) |
[section] and [section "subsection"] (subsection in double quotes) |
; or # to end of line |
= |
Trailing backslash folds the next line | Subsection names case-sensitive; section/variable names case-insensitive; supports [include] path = ... directives |
| systemd unit files | [Unit], [Service], [Install] |
; or # at line start |
= (no whitespace around = allowed before the value) |
Trailing \ joins the next line |
Values unquoted; same Key= can repeat to append; section/key names case-sensitive |
.editorconfig |
[glob-pattern] (e.g., [*.{js,py}]) |
; or # |
= |
Not in spec | Section header is a glob, not a name; properties like indent_style, indent_size |
| Generic INI (no formal spec) | [Section] |
Either ; or # depending on parser |
= or : depending on parser |
Parser-defined | No standardised nesting — [a.b] is convention only |
| Property | INI | TOML | YAML |
|---|---|---|---|
| Spec status | No official spec — dialect-defined | TOML 1.0.0 (Jan 11, 2021), 1.1.0 (Dec 2025) | YAML 1.2.2 (Oct 2021) |
| Native datatypes | None — every value is a string; the reader parses to bool/int | String (4 variants), int (dec/hex/oct/bin), float, bool, 4 datetime variants, array, table | String, int, float, bool, null, sequence, mapping (with implicit coercion in 1.1) |
| Nested structures | Flat — convention [parent.child] is just a section name with a dot in it |
Tables, inline tables, arrays of tables, dotted keys for nesting | Arbitrary nesting via indentation |
| Whitespace significance | None (inside the line) | None inside tables | Significant — wrong indent breaks parsing |
| Comments | ; and/or # depending on dialect |
# to end of line |
# to end of line |
| Multi-line values | Backslash continuation (Git, systemd) or indent (configparser) | Triple-quoted strings (""" or ''') |
Block scalars (|, >) |
| Quoting required | No — values are bare strings | Strings must be quoted; bare tokens reserved for literals | Mostly optional — but unquoted no, on, yes may parse as booleans (Norway problem in 1.1) |
| Typical use | Flat per-app config in scripts and daemons (php.ini, my.cnf, systemd) | Typed nested config for tooling (Cargo.toml, pyproject.toml, hugo.toml) | CI/CD, Kubernetes, Ansible — anywhere you need deep nesting |
| Best fit | Legacy systems, simple flat config, small [section]/key=value files |
New projects needing types + nesting without indent pitfalls | Deeply structured manifests where readability of trees matters |
No. INI grew out of Microsoft Windows configuration files in the 1980s and was never standardised by an SDO. The closest thing to a reference is the behaviour of the Windows API functions GetPrivateProfileString / WritePrivateProfileString, but every parser since has added or trimmed features — Python's configparser, Git's config code, systemd's unit-file parser, and PHP's parse_ini_file all behave differently on edge cases like comment markers, line continuation, and quoting. That's why this formatter is conservative: it normalises whitespace and structure without inventing syntax that some parsers would reject.
Not in any formal sense. The square-bracket section header is a flat namespace — [database] and [database.replica] are two unrelated sections as far as the spec is concerned, even though many tools use the dot as a visual hint of hierarchy. Python's configparser explicitly stores sections in a flat dict; dotted names are just naming convention. If you need real nesting move to TOML (which has tables, inline tables, and dotted keys with proper semantics) or YAML. For Git config specifically, the [section "subsection"] form with a quoted subsection is the closest INI gets to a two-level hierarchy.
No — every value is a string at the file-format level. The reading library is responsible for parsing port=8080 as an integer or enabled=true as a boolean. Python's configparser exposes this with explicit getint(), getfloat(), and getboolean() methods (the boolean parser recognises yes/no, on/off, true/false, 1/0). PHP's parse_ini_file auto-coerces some literal tokens (true, false, null, yes, no) when called in INI_SCANNER_TYPED mode and leaves everything as a string otherwise. Don't assume types survive a round trip through any INI parser — explicit conversion in your code is the only safe bet.
; vs # for comments — which should I use?It depends on what's going to read the file. Classic Windows .ini files use ; exclusively, and that's still the safest default for any consumer that targets the Windows API. Python's configparser accepts both # and ; as full-line comment prefixes by default. Git's config parser accepts both. systemd's parser accepts both. If you're writing for a known target, follow that project's convention — php.ini uses ;, redis.conf uses #, .gitconfig typically uses ;. If you're authoring a portable config consumed by a tool you don't control, prefer ; for the broadest compatibility. Inline comments (key = value ; trailing comment) are particularly dialect-sensitive — many parsers including default configparser don't strip them, so the comment becomes part of the value.
Yes, but the mechanism varies by dialect. Git config and systemd join a line ending in \ with the next line (and replace the backslash with a space, in systemd's case). Python's configparser uses a different approach: a value continues onto the next line as long as that next line is indented more deeply than the first line of the value — no backslash needed. Classic Windows INI has no documented multi-line mechanism, so values that need newlines are typically escaped with literal \n and unescaped by the application. Quoted strings spanning lines are not part of the common INI core and shouldn't be relied on.
Git uses INI as its config format but needed a second level of namespacing — one for the section type (remote, branch, submodule, url) and one for the specific instance (the remote name, the branch name, the URL pattern). The standard syntax is [remote "origin"] with the subsection in double quotes, separated from the section name by a space. Subsection names are case-sensitive while the outer section name is case-insensitive, and double quotes plus backslashes inside the subsection name must be escaped (\" and \\). Git also supports a deprecated [section.subsection] dotted form for compatibility, but the quoted form is what git config --edit writes. The [include] section is special — Git follows path = ... directives inside it to load additional config files.
Use INI when (a) you're maintaining an existing INI-format file that downstream tools already parse, (b) the config is genuinely flat — sections of key=value strings with no nesting or typed values needed, or (c) you're writing for a runtime that only ships an INI parser (legacy Windows apps, older PHP code, embedded scripts). Use TOML when you're starting a new project and want typed values (int, float, bool, datetime), real nested tables, arrays of tables, and a single official spec that every modern parser implements the same way. The Rust, Python, and Go ecosystems have all standardised on TOML for new tooling (Cargo.toml, pyproject.toml, hugo.toml) precisely because INI's dialect drift and string-only values became painful at scale.
.editorconfig files — are those INI?Yes, .editorconfig uses INI syntax but with one twist: the section header is a glob pattern for matching files, not a name. [*.py], [*.{js,ts,jsx,tsx}], and [Makefile] are all valid section headers, and the properties inside (indent_style = space, indent_size = 2, end_of_line = lf, charset = utf-8, trim_trailing_whitespace = true, insert_final_newline = true) apply to any file matching that glob. The root = true property at the top of a file tells the editor to stop searching parent directories for further .editorconfig files. This formatter handles .editorconfig files cleanly — section headers are preserved verbatim including the glob characters.
No. The INI Formatter runs entirely in your browser — your file never leaves the tab. There are no network requests after the initial page load, so secrets that often live in INI files (database passwords in my.cnf, API tokens in tool configs, OAuth client IDs in .gitconfig) stay on your machine. The page also works offline once loaded. If you also work with JSON or YAML, the JSON Formatter, YAML to JSON, and Validate YAML tools run the same way.