TOML exists because YAML is too complex for configuration files and JSON doesn't support comments. Tom Preston-Werner created TOML in 2013 with a single goal: a config format that maps unambiguously to a hash table (dictionary/object) and is easy for humans to read and write. No indentation footguns. No implicit type coercion. No 86-page specification.
TOML looks like an INI file that grew up and got a type system. If you've ever written a config file with [section] headers and key = value pairs, you already know 80% of TOML. The remaining 20% — arrays of tables, inline tables, and multiline strings — handles the cases where INI falls short.
TOML v1.0.0 was finalized in January 2021, giving it a stable specification after eight years of development. It's now the standard for Rust's Cargo, Python's pyproject.toml, Hugo, Netlify, and an expanding set of tools that want config simplicity without YAML's baggage.
TOML Syntax: Clear, Typed, and Unsurprising
TOML's syntax is built on key-value pairs organized into tables (sections):
# This is a comment
title = "My Application"
[server]
host = "localhost"
port = 8080
debug = false
[database]
connection_string = "postgres://localhost/mydb"
max_connections = 25
connection_timeout = 30.0
[database.pool]
min_size = 5
max_size = 25Key features of TOML syntax:
- Comments use
#and run to end of line. No multiline comments. - Keys are bare (unquoted) if they contain only letters, digits, dashes, and underscores. Otherwise, quote them:
"key with spaces" = "value". - Tables (sections) use
[table_name]headers, like INI files. Dotted keys ([database.pool]) create nested tables. - No indentation requirements. Indentation is purely cosmetic. You can indent for readability or not — the parser doesn't care.
TOML's Type System
TOML has the strongest type system of any common config format:
| Type | Example | Notes |
|---|---|---|
| String | name = "hello" | Always quoted. Basic (double) or literal (single) quotes. |
| Integer | port = 8080 | 64-bit signed. Supports _ separators: 1_000_000 |
| Float | pi = 3.14159 | 64-bit IEEE 754. Supports inf, nan |
| Boolean | debug = true | Only true and false. Not "yes", not "on". |
| Offset Date-Time | created = 2026-03-19T14:30:00Z | ISO 8601 with timezone. Native type, not a string. |
| Local Date-Time | alarm = 2026-03-19T07:00:00 | No timezone offset. |
| Local Date | birthday = 1990-05-15 | Date only, no time. |
| Local Time | start = 09:00:00 | Time only, no date. |
| Array | ports = [8080, 8443] | Must be same type. Multiline allowed. |
| Inline Table | point = {x = 1, y = 2} | Single-line table. Cannot be multiline. |
TOML is the only common data format with native date/time types. JSON uses strings. YAML auto-parses ISO dates but that behavior is inconsistent across parsers. CSV has no types at all. In TOML, date = 2026-03-19 is unambiguously a date — not a string that happens to look like a date.
Tables, Nested Tables, and Arrays of Tables
TOML's table syntax is where it goes beyond INI files:
# Simple table
[owner]
name = "John"
email = "john@example.com"
# Nested table (equivalent to owner.address)
[owner.address]
street = "123 Main St"
city = "Springfield"
# Array of tables (array of objects)
[[products]]
name = "Widget"
price = 9.99
[[products]]
name = "Gadget"
price = 19.99The [[double_bracket]] syntax for arrays of tables is TOML's most powerful and least intuitive feature. Each [[products]] block creates a new element in an array. This maps to JSON's "products": [{"name": "Widget", "price": 9.99}, {"name": "Gadget", "price": 19.99}].
This syntax works well for lists of structured items (packages, servers, routes) but becomes verbose for deeply nested arrays. A three-level nested array of tables requires [[level1.level2.level3]] headers for each element at the deepest level — manageable for config files, unwieldy for complex data.
Where TOML Is the Standard
TOML has been adopted as the default config format by several major ecosystems:
| Ecosystem | File | Purpose |
|---|---|---|
| Rust (Cargo) | Cargo.toml | Package manifest, dependencies, build config |
| Python (PEP 518/621) | pyproject.toml | Project metadata, build system, tool config |
| Go modules | go.sum uses TOML-like format | Dependency checksums |
| Hugo | config.toml | Site configuration |
| Netlify | netlify.toml | Build and deploy configuration |
| pip | pip.conf supports TOML | pip configuration |
| Black (Python) | [tool.black] in pyproject.toml | Formatter configuration |
| Ruff (Python) | [tool.ruff] in pyproject.toml | Linter configuration |
The pattern: newer tools and ecosystems favor TOML. Older ecosystems (Java, .NET, DevOps) remain on XML or YAML. Rust's early adoption of TOML gave it legitimacy; Python's PEP 518 (2017) and PEP 621 (2020) made it mainstream.
TOML vs. YAML: Different Tools for Different Jobs
TOML and YAML serve the same purpose (config files) but make different tradeoffs:
| Feature | TOML | YAML |
|---|---|---|
| Indentation | Irrelevant (cosmetic only) | Structural (bugs if wrong) |
| Type coercion | None (all types explicit) | Aggressive (Norway problem) |
| Boolean values | true/false only | 12+ variants in YAML 1.1 |
| Date types | Native (4 date/time types) | Auto-detected from strings |
| Deep nesting | Verbose (long table headers) | Natural (indentation) |
| Comments | # (line only) | # (line only) |
| Spec complexity | ~30 pages | ~86 pages |
| Learning curve | Minutes | Hours (including footguns) |
TOML wins for flat-to-moderately-nested configuration. YAML wins for deeply nested structures (like Kubernetes manifests with 5+ nesting levels). For a simple app config with database settings, feature flags, and API keys, TOML is the safer choice. For orchestrating 50 containers with networking, volumes, and resource limits, YAML's deep nesting handles it better.
You can convert TOML to YAML or YAML to TOML when switching between ecosystems. The conversion is lossless in both directions for data that fits TOML's structure.
Where TOML Falls Short
TOML's simplicity is also its limitation:
- Deep nesting is verbose. A 4-level nested structure requires table headers like
[level1.level2.level3.level4]for each leaf, making the file harder to read than equivalent YAML. TOML is designed for 2-3 levels of nesting maximum. - No anchors or references. YAML's anchors let you define a value once and reference it multiple times. TOML has no equivalent — you must duplicate values or handle deduplication in application code.
- Heterogeneous arrays are forbidden.
mixed = [1, "two", true]is invalid TOML. Every element in an array must be the same type. This prevents TOML from representing some valid JSON structures. - No multi-line inline tables. Inline tables (
{key = "value"}) must fit on one line. For complex nested objects, you must use full[table]sections. - Smaller ecosystem. YAML has decades of tooling, editor support, and library maturity. TOML libraries exist for every major language but are generally less battle-tested.
Converting TOML to Other Formats
TOML sits between INI (simpler) and YAML (more complex) in the config format spectrum:
| Conversion | What Happens | Data Loss? |
|---|---|---|
| TOML to JSON | Direct mapping. Date types become ISO 8601 strings. | Date type information (becomes string) |
| TOML to YAML | Direct mapping. Dates preserved if parser supports them. | No |
| TOML to CSV | Only works for flat tables or arrays of tables. | Yes — nesting, types |
| TOML to XML | Tables become elements. Arrays become repeated elements. | No (structurally lossless) |
| TOML to INI | Works for single-level tables. Nested tables may be flattened. | Yes — types, nesting |
| TOML to ENV | Only top-level key-value pairs. Everything becomes a string. | Yes — types, nesting, arrays |
TOML is the right tool for a specific job: configuration files that humans read and edit, with moderate structure and explicit types. It doesn't try to be a general data format like JSON or a do-everything serialization language like YAML. That narrow focus is its strength — fewer features means fewer surprises.
If your config file has sections, key-value pairs, maybe some lists and a few nested sections, TOML is the best choice. If you need deeply nested structures, heterogeneous arrays, or anchors, use YAML. If you need to exchange data between systems, use JSON. TOML's lane is clear, and it stays in it.