TOML Formatter
Canonical formatting for TOML. Paste your document and receive a normalized version with consistent spacing, string quoting, and table layout. Everything runs in your browser.
What this tool does
It parses a TOML document, builds the value tree in memory, and re-emits it as canonical TOML — with consistent spacing around =, normalized string quoting, deterministic table layout, and an optional sort order. Parsing follows the TOML 1.0 spec (dotted keys, every date variant, hex/octal/binary numbers); emission honours the option set on the widget.
The intent it closes: "I want this TOML file to look the way the project would have produced it if everyone had been disciplined." Useful when a config has been touched by three tools and four people, when a generator dumps unformatted output, or when you want a diff that only shows real changes rather than whitespace churn.
One up-front trade-off worth flagging: comments are dropped on re-emit. The widget hint says this; the rest of the page repeats it because it's the single most important thing to know.
When you'd reach for it
- Normalize a hand-edited
pyproject.tomlorCargo.toml. Get spacing, quoting, and ordering back to the form your team review expects. - Reduce diff noise before committing. Format both sides of a refactor so the only changes left are real ones.
- Validate while you format. If parsing fails, the status bar says so — a malformed config never produces output, so a clean format is also a syntax-valid file.
- Alphabetize for searchability. Pick the Alphabetical sort to make a giant table scannable.
- Inline small tables. Switch Tables to Auto with a small Inline ≤ threshold to keep two-key tables compact while letting bigger ones expand.
- Match a project style guide. Most rust-ecosystem projects use basic quotes, no trailing commas, spaced arrays — those are the defaults here.
How formatting works
One click of Format runs three stages.
1. Parse the input
The full TOML 1.0 grammar is parsed into a JavaScript value tree. Dotted keys collapse into nested tables, dates become real Date-shaped values, integers preserve their hex/octal/binary intent. If any of this fails, the status bar shows the parser error and no output is written — formatting and validation share the same parser.
2. Order keys
Each table's keys are walked in one of three orders. Preserve order writes them in input order. Alphabetical sorts case-sensitively. Alpha, tables last sorts case-sensitively, but pushes any key whose value is a table (or array of tables) to the end — useful for keeping scalar config at the top of every section.
3. Emit, deciding inline vs block per table
For each table, the formatter decides whether to write it inline ({ a = 1, b = 2 }) or as a section ([name]). Always inline and Always block are the explicit choices; Auto uses the Inline ≤ threshold and only inlines leaf tables (no nested tables of their own). Arrays-of-tables ([[…]]) are always block. Section blocks get a blank line between them.
Options reference
Sort
![]()
Preserve order keeps the order keys appeared in your input. Alphabetical sorts keys in each table A-Z. Alpha, tables last is the same but moves table-valued keys after scalars — handy when you want name and version before [dependencies] in every section.
Tables (inline mode)
![]()
Auto consults the Inline ≤ threshold; tables small enough and shallow enough are emitted inline. Always block writes every table as [section] headers — verbose but uniform. Always inline writes everything as { … }; mostly useful for snippets, not full config files.
Inline ≤
![]()
The maximum number of keys a leaf table can have to be emitted inline under Auto. 0 means "never inline anything from Auto mode" — effectively a softer Always block. Try 3 for a balance.
Arrays
![]()
Spaced writes [1, 2, 3] with a space after each comma. Compact writes [1,2,3]. Rust and Python ecosystems generally use Spaced; some Go and embedded configs use Compact.
Quotes
![]()
Basic uses standard double quotes ("hello") and applies escapes when needed. Literal uses single quotes ('hello') — no escapes are interpreted, so backslashes stay literal. Auto picks per-string: literal quotes for strings without single quotes and with characters that would need escaping in basic mode (e.g. Windows paths), basic otherwise.
Trailing ,
![]()
Off writes [1, 2]. On writes [1, 2,]. TOML 1.0 allows both; trailing commas reduce diff churn when arrays grow.
Wrap @
![]()
Soft wrap column for inline arrays. 0 means "no wrap — arrays stay on one line regardless of width". Set to 80 to break long arrays onto multiple lines once they pass column 80.
Output format
- Encoding: UTF-8, no BOM.
- Line endings: LF (
\n). A trailing newline is appended when the document is non-empty. - Spacing: exactly one space on each side of
=. Section headers get a blank line before them (except the first). - Comments: not emitted. The parser doesn't carry them through; if you need them, keep your source file around.
- Whitespace inside arrays: driven by the Arrays option, plus Wrap @ for long inline arrays.
- Download:
formatted.tomlwithtext/plaincontent type.
Example
Input:
title="Example"
[owner]
name="Tom"
[database]
enabled=true
ports=[8001,8001,8002]
connection_max=5000
Output (defaults — Preserve, Auto, Basic quotes, Spaced arrays, no trailing comma):
title = "Example"
[owner]
name = "Tom"
[database]
enabled = true
ports = [8001, 8001, 8002]
connection_max = 5000
Note the blank line before each section, the space on both sides of =, and the spaced commas inside the array. The duplicate 8001 survives — formatting doesn't deduplicate values.
Recipes by intent
Clean up pyproject.toml for a PR
Sort Preserve order, Tables Always block, Quotes Basic, Arrays Spaced, Trailing , off. Matches Poetry's expected style.
Alphabetize a giant config you have to search by eye
Sort Alpha, tables last. Scalars (name, version, edition) cluster at the top of every section; nested tables drop to the bottom. Makes Cargo.toml with many features readable.
Compact a config for an embedded device or env var
Tables Always inline, Arrays Compact, Inline ≤ 64. Produces the smallest valid TOML, useful when serializing config into a single env var or QR code.
Wrap long arrays automatically
Wrap @ 80. Inline arrays past column 80 break across lines, keeping diffs reviewable when an array grows. Below the threshold they stay on one line.
Diff-friendly canonical form
Sort Alphabetical, Trailing , on, Quotes Basic. With everyone formatting before commit, diffs only show semantic changes — no whitespace, no key reordering.
Limits and performance
- Memory-bound. The parser builds the full value tree before emission, so input, tree, and output all sit in memory at once.
Cargo.lock-sized files (thousands of lines) are instant; multi-megabyte TOML can take a second or two. - Comments are not preserved. Parser → tree → emit drops them. If comments matter, keep the source file and use this tool only on a generated copy.
- Key order survives only in Preserve mode. Alphabetical orderings rewrite the layout completely; that's the trade-off.
- The textarea is the slow bit on huge files. Around 50 MB+ of output, rendering it back into the right-hand pane can lag even after the format completes. Use Download .toml instead of Copy.
Errors and how to fix them
"Parse error" / status bar goes red on click
The input isn't valid TOML 1.0. The most common causes: unclosed quotes, an array that opens with [ but never closes, a missing = between key and value, or a duplicate key inside the same table. Try the TOML Validator — it points at the exact line.
Output is empty
The root of a TOML document must be a table. If your input is empty, or you passed in a TOML fragment that wasn't a top-level table (just a value), the formatter refuses to emit anything.
My # comments disappeared
Expected. The parser doesn't carry comments through to the value tree, so the emitter has nothing to write. Keep your original file as the source of truth; format a copy for diffs or shipping.
Key order changed unexpectedly
Either Sort isn't on Preserve order, or your input had the same dotted key written in two different forms (a.b = 1 and [a]\nb = 1) — both collapse to the same tree, and the emitter picks one. Pick Preserve order and consolidate the duplicates in your source.
String quoting style flipped
The Quotes option canonicalizes every string. If your input had mixed quoting, the output normalizes it. Switch to Auto if you want the formatter to pick per-string instead of forcing one style everywhere.
FAQ
Will formatting change the meaning of my config?
No. The tree is semantically identical to your input — only whitespace, quote style, key order (when sorted), and inline-vs-block decisions change. Anything that parses to the same value tree round-trips to the same effective config.
Does it handle the full TOML 1.0 spec?
Yes, including dotted keys, every date variant, hex/octal/binary integers, and array-of-table syntax ([[…]]). Anything the spec disallows (mixed array types, duplicate keys) errors out at parse time.
Is my data uploaded?
Never. The parser and emitter run entirely in your browser. You can disconnect from the network before clicking format and it still works — safe for files with secrets or API keys.
Why is there a blank line between sections?
Because that's the dominant convention across Rust, Python, and Go ecosystems, and because a blank line makes section boundaries scannable. It's emitted automatically and isn't configurable on this widget.
Can I preserve comments somehow?
Not in a pure parse-and-re-emit flow like this one. If you need to keep comments, hand-edit, or use a comment-preserving CST library in your own pipeline. This tool optimizes for a clean, deterministic output.