TOML in practice
TOML (Tom’s Obvious Minimal Language) is the configuration format of Rust’s Cargo.toml, Python’s pyproject.toml (used by pip, Poetry, uv, Ruff, Black and pytest), Hugo and Netlify sites, rust-toolchain.toml, Starship, Alacritty and many more. It maps directly to a hash table: key = value pairs grouped under [table] headers, with [[array.of.tables]] for repeated sections such as Cargo’s [[bin]] targets.
Files written by hand drift over time: inconsistent spacing around =, inline tables that grew too long, arrays squeezed onto one line. This page fixes the layout with taplo, the formatter behind the Even Better TOML extension for VS Code, compiled to WebAssembly so it runs in your browser.
Validation first, then formatting
taplo is deliberately lenient: it will happily format a file that Cargo or pip would reject. To avoid giving you a pretty but broken file, the input is first parsed with smol-toml, a strict TOML 1.0 parser. Any error stops the run and is shown with its line and column. After formatting, the output goes through the strict parser a second time, so you never receive a file that is less valid than the one you pasted.
Workflow: paste the file or drop a .toml file, press Ctrl/Cmd+Enter to format, and Ctrl/Cmd+Shift+C to copy. Ctrl/Cmd+K opens the command palette, where the Tree view and the TOML to JSON and TOML to YAML conversions live. TOML has no minified form, so there is no minify step for this format. Your config, including any registry tokens in .cargo/config.toml, is processed locally and not transmitted.
Options
- Align = signs pads keys within a block so the
=signs form a column. It reads nicely in short, stable sections like[package], but every rename re-aligns its neighbours and inflates diffs. - Trailing comma in multi-line arrays adds a comma after the last element of an array that spans several lines. Adding a dependency feature then touches one line in version control instead of two.
- Expand long arrays breaks arrays that exceed the line width onto one element per line. Turn it off to keep long arrays on one line regardless of width; short arrays are collapsed back to one line either way.
- Indent sub-tables indents
[tool.ruff.lint]under[tool.ruff]according to its depth, which some people find easier to scan in longpyproject.tomlfiles. TOML ignores indentation, so this is purely visual.
Indent size and line width apply too. Sort keys orders keys alphabetically within each run of consecutive lines; a blank line or comment starts a new run, and table headers keep their positions. Avoid sorting [package] in Cargo.toml, where name before version is the convention.
Rules that surprise people
- A table can only be defined once. Writing
[dependencies]twice, or creating[server]afterserver.port = 8080already implied it, is an error, not a merge. - Inline tables belong on one line in TOML 1.0, with no trailing comma. The validator accepts the relaxed TOML 1.1 form, but older parsers do not, so a long
{ version = "1", features = [...] }entry is safer as a[dependencies.serde]table. - Leading zeros are illegal in integers (
port = 08080) to avoid octal confusion; use0ofor octal. - Dates are real types.
release = 2026-09-14is a local date, not a string, and2026-13-01fails validation. Integers are 64-bit and are kept exactly, even beyond JavaScript’s safe range.
Examples
Cargo.toml with uneven spacing
Spacing around = and inside inline tables is normalised, and the long sqlx feature list is expanded because it exceeds the line width.
[package]
name="order-service"
version = "0.4.2"
edition="2021"
rust-version = "1.80"
[dependencies]
axum={version="0.7",features=["macros"]}
tokio = { version = "1", features = ["rt-multi-thread","macros","signal"] }
serde={version="1.0",features=["derive"]}
sqlx = { version = "0.8", default-features = false, features = ["postgres","runtime-tokio","tls-rustls","uuid","chrono"] }
[[bin]]
name="order-service"
path="src/main.rs"
[package]
name = "order-service"
version = "0.4.2"
edition = "2021"
rust-version = "1.80"
[dependencies]
axum = { version = "0.7", features = ["macros"] }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "signal"] }
serde = { version = "1.0", features = ["derive"] }
sqlx = { version = "0.8", default-features = false, features = [
"postgres",
"runtime-tokio",
"tls-rustls",
"uuid",
"chrono",
] }
[[bin]]
name = "order-service"
path = "src/main.rs"
pyproject.toml with aligned keys and indented tool tables
Keys in each table get a common = column and [tool.ruff.lint] is indented one level deeper than [tool.ruff].
[project]
name = "invoice-parser"
version = "1.3.0"
requires-python = ">=3.11"
dependencies = ["httpx>=0.27", "pydantic>=2.7", "python-dateutil"]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
ignore = ["E501"]
[tool.pytest.ini_options]
addopts = "-ra --strict-markers"
testpaths = ["tests"]
[project]
name = "invoice-parser"
version = "1.3.0"
requires-python = ">=3.11"
dependencies = ["httpx>=0.27", "pydantic>=2.7", "python-dateutil"]
[tool.ruff]
line-length = 100
target-version = "py311"
[tool.ruff.lint]
select = ["E", "F", "I", "UP", "B"]
ignore = ["E501"]
[tool.pytest.ini_options]
addopts = "-ra --strict-markers"
testpaths = ["tests"]
netlify.toml with an array of tables
Each [[redirects]] entry is kept as a separate table in an array, with consistent spacing inside every entry.
[build]
command="npm run build"
publish="dist"
[[redirects]]
from="/api/*"
to="https://api.example.com/:splat"
status=200
force=true
[[redirects]]
from="/old-pricing"
to="/pricing"
status=301
[[headers]]
for="/*"
[headers.values]
X-Frame-Options="DENY"
Content-Security-Policy="default-src 'self'"
[build]
command = "npm run build"
publish = "dist"
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
force = true
[[redirects]]
from = "/old-pricing"
to = "/pricing"
status = 301
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
Content-Security-Policy = "default-src 'self'"
Common errors and how to fix them
| Error | Cause | Fix |
|---|---|---|
Trying to redefine an already defined table or valueExplained | The same key appears twice in a table, the same [table] header appears twice, or a dotted key already created the table you are now declaring. | Merge the two sections into one, or remove the duplicate key; the position points at the second definition. |
Unfinished string | A quoted value has no closing quote on the same line. Basic strings (“…”) and literal strings (‘…’) cannot span lines. | Close the quote, or use a multi-line string with triple quotes (“”" or ‘’') for text that needs line breaks. |
Expected comma or end of structure | An array or inline table is missing a comma between elements or its closing bracket. | Add the missing comma or ] / } at the reported position. |
Illegal leading zero | An integer such as 0755 or 08080 starts with zero, which TOML forbids. | Drop the zero, write file modes with the 0o prefix (0o755), or quote the value if it is really an identifier. |
Illegal character in key | A bare key contains a space, a non-ASCII letter or punctuation; bare keys allow only A–Z, a–z, 0–9, _ and -. | Quote the key: “display name” = “Aisha”. |
Frequently asked questions
How do I format Cargo.toml automatically?
cargo fmt only formats Rust code. Install taplo-cli and run taplo fmt, or use the Even Better TOML extension in VS Code; both use the same taplo engine as this page.
Does formatting keep comments?
Yes. Comments stay on the lines they annotate, and Sort keys never moves a key across a comment, so a note cannot end up above the wrong entry.
Which TOML version is supported?
Validation follows TOML 1.0.0, the version most tools implement, and also accepts the small TOML 1.1 relaxations such as multi-line inline tables. If an older tool must read the file, avoid those 1.1 forms.
TOML or YAML for configuration?
TOML is stricter and has no indentation rules, which suits flat or moderately nested settings. YAML handles deep nesting and anchors better. The TOML vs YAML guide compares them in detail.
Can I convert TOML to JSON?
Yes, with the TOML to JSON converter. JSON has no date type, so TOML dates and times come out as strings.