T tomlkit·org
Inspect Formatter Validator Lint Stats Keys Query Set value pyproject Cargo Convert TOMLJSON JSONTOML TOMLYAML YAMLTOML INITOML TOMLINI .envTOML TOML.env TOMLTS TOMLCSV CSVTOML TOMLXML TOML.properties .propertiesTOML Transform Sort keys Flatten Unflatten Redact Minify Generate Go struct Rust struct Python types JSON Schema Compare Diff Merge Lockfile diff requirements.txtpyproject

TOML Formatter

updated 28 May 2026

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.

comments are kept and re-attached to their key

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.

Comments are kept. They used to be dropped — a parser produces a value tree, and a value tree has nowhere to put them — so this tool now reads the source a second time, on its own, recording only which key or table header each comment was written against. The emitter looks each one up as it writes that line out, which means a comment follows its key even when Sort moves it. The status line reports how many were found and how many landed.

Two cases still lose one: a comment on a key inside a table you have asked to be written inline has no line of its own to sit on, and a comment attached to something the emitter does not write at all goes with it. Both are counted, and the status line says so rather than staying quiet.

When you'd reach for it

  • Normalize a hand-edited pyproject.toml or Cargo.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

Sort dropdown showing Preserve order

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)

Tables dropdown showing Auto

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 ≤

Inline-max number input showing 0

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

Arrays dropdown showing Spaced

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

Quotes dropdown showing "basic"

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 ,

Trailing-comma dropdown showing off

Off writes [1, 2]. On writes [1, 2,]. TOML 1.0 allows both; trailing commas reduce diff churn when arrays grow.

Wrap @

Wrap-at number input showing 0

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: kept by default, re-attached above the key or header they were written against, with an end-of-line comment restored two spaces after the value. Set Comments to Drop for the old behaviour.
  • Whitespace inside arrays: driven by the Arrays option, plus Wrap @ for long inline arrays.
  • Download: formatted.toml with text/plain content 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 survive a re-layout, not a re-shaping. They are matched to a key path, so sorting and re-indenting keep them. Inlining a table removes the lines its comments were attached to, and those are reported as not kept rather than silently lost.
  • 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

Check the Comments option is on Keep, then read the status line — it says how many comments were found and how many were written back. A gap between the two means the missing ones were attached to keys that no longer have a line of their own: usually a table pushed inline by Tables or Inline ≤. Turn inlining off and they come back.

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.

How are comments preserved without a CST?

With a second pass rather than a bigger parser. The value tree comes from the TOML parser as before; alongside it a small scanner walks the same source text and builds one map from key path to the comments written against it — the block above a line, and the trailing one after the value. It has to understand quoting, multi-line strings and wrapped arrays well enough not to mistake a # inside a string for a comment, which is most of its code. The emitter then asks that map for a path as it writes it. The two agree because they build the same path string, and neither needs to know about the other.

The limitation of the approach is honest and visible: a comment can only be written back if the key it belongs to still gets a line. Nothing is guessed at, and nothing is moved to a place it was not.