Skip to content
Back to Blog
informational

What Is TOML? The Config Language That Beat YAML

2026-05-17 9 min read

What TOML Actually Is

TOML stands for Tom's Obvious, Minimal Language. Tom Preston-Werner — one of the co-founders of GitHub — created it in 2013 because he was frustrated with the configuration file formats available at the time. The first stable release, TOML v1.0.0, landed in January 2021 after nearly eight years of refinement, which tells you something about how seriously the spec was taken. At its core, TOML is a configuration file format. It is not a general-purpose data serialization format like JSON, and it is not a markup language. Its entire purpose is to be easy for a human to read and write, while mapping unambiguously to a hash table — what most programming languages call a dictionary, map, or object. A minimal TOML file looks like this: ``` title = "My Application" version = "2.4.1" debug = false [database] host = "localhost" port = 5432 max_connections = 100 ``` Every value has an explicit type. The string `"localhost"` is a string. The integer `5432` is an integer. The boolean `false` is a boolean — not the string `"false"`, not `0`, not `null`. This strictness is deliberate and is one of the main reasons developers reach for TOML over alternatives. You never have to wonder whether your port number will be parsed as a string or an integer depending on which YAML library you happen to be using that week.

Why YAML Became a Problem Worth Solving

YAML has a reputation for being human-friendly, and in small doses it is. But as configuration files grow in complexity, YAML's design choices start creating real problems. The most infamous is the Norway Problem. In YAML 1.1 — which is still what many parsers implement — the two-letter country code `NO` is parsed as the boolean `false`. So a configuration like `country: NO` would silently set your country field to `false`. The same applies to `yes`, `no`, `on`, `off`, `true`, and `false` in various capitalizations. YAML 1.2 fixed this, but parser support is inconsistent, and you cannot always know which version of the spec your runtime is using. Then there is significant whitespace. YAML uses indentation to define structure, which means a single misplaced space can change the meaning of your entire file — or break it entirely — without any obvious error message. Anyone who has spent 45 minutes debugging a CI/CD pipeline only to find a two-space versus four-space inconsistency knows exactly how this feels. YAML also supports multiple ways to write the same thing. Scalars can be plain, single-quoted, double-quoted, literal block, or folded block style. Sequences can be flow style or block style. This flexibility sounds like a feature but in practice means that two developers writing the same configuration will produce files that look completely different, making diffs harder to read and code reviews less useful. None of this means YAML is bad for every use case. Kubernetes manifests and GitHub Actions workflows use it effectively. But for application configuration — where correctness and predictability matter more than flexibility — these quirks add up.

How TOML Solves the Readability Problem

TOML's design philosophy is that a configuration file should have exactly one obvious way to express any given piece of data. This constraint sounds limiting but produces files that are remarkably consistent across projects and teams. There are six scalar types: string, integer, float, boolean, offset date-time, and local date. That last category is worth highlighting. TOML has first-class support for dates and times in RFC 3339 format, so you can write `created_at = 2024-03-15T09:30:00Z` and know it will be parsed as a datetime object, not a string. YAML can represent dates too, but the behavior varies by parser and version. Structure in TOML is expressed through tables (sections) and arrays of tables. A table is declared with a header in square brackets: `[server]`. An array of tables uses double square brackets: `[[products]]`. These conventions are unambiguous and visually distinct. Here is a real-world example showing how a Rust application's Cargo.toml might define dependencies: ``` [dependencies] serde = { version = "1.0", features = ["derive"] } tokio = { version = "1", features = ["full"] } reqwest = "0.12" ``` The inline table syntax `{ version = "1.0", features = ["derive"] }` is compact for simple cases. For complex nested structures, you use full table headers instead. The language gives you two modes and clear rules for when to use each. Comments use the `#` character, identical to Python and shell scripts, which means most developers already know the syntax before they read a single line of documentation.

Where TOML Has Won: Real Adoption Numbers

TOML's most visible adoption is in the Rust ecosystem. Cargo, Rust's package manager, uses TOML for all package manifests. Every Rust project that has ever been published to crates.io — over 150,000 packages as of early 2025 — has a `Cargo.toml` file. This alone represents one of the largest real-world deployments of any configuration format. Python adopted TOML as a standard in PEP 518 (2016) and PEP 621 (2021), making `pyproject.toml` the canonical location for project metadata and build configuration. Tools like Poetry, Hatch, Flit, and PDM all read from `pyproject.toml`. The `[tool.pytest.ini_options]` table in a `pyproject.toml` file replaces the old `pytest.ini` format. The `[tool.ruff]` table configures the Ruff linter. One file, one format, multiple tools. Hugo, the static site generator, switched from YAML and JSON to TOML as its default configuration format. The Hugo team cited TOML's explicitness and lack of surprising type coercions as the primary reasons. The Python standard library added `tomllib` in Python 3.11 (released October 2022), meaning you can parse TOML files with zero external dependencies on any modern Python installation. That kind of standard library inclusion signals genuine mainstream status. Go, .NET, and JavaScript all have well-maintained TOML libraries. The `@iarna/toml` package on npm has millions of weekly downloads. This is not a niche format used by one language community anymore.

TOML's Genuine Limitations

TOML is not the right tool for every job, and pretending otherwise would be dishonest. The most significant limitation is that TOML does not handle deeply nested data structures gracefully. If you need more than two or three levels of nesting, the syntax becomes awkward. You end up with headers like `[servers.production.database.replica]` that are technically valid but hard to follow. JSON or even YAML handles deep nesting more naturally because both formats were designed with general-purpose data representation in mind. TOML is also verbose for large arrays of complex objects. The `[[products]]` syntax for arrays of tables requires repeating the header for every item, which means a list of 50 products becomes 50 `[[products]]` headers scattered through your file. For that kind of data, JSON or a database is more appropriate. There is no support for anchors or aliases, which YAML provides through `&anchor` and `*alias` syntax. In a large YAML configuration, you can define a block of settings once and reference it in multiple places. TOML has no equivalent. If you need to repeat configuration across environments, you either duplicate it or handle the merging in application code. Finally, TOML files cannot be streamed. Unlike JSON Lines or certain YAML configurations, a TOML file must be read and parsed in full before any values are accessible. For very large configuration files — which is itself a sign of a design problem, but it happens — this can matter. None of these limitations make TOML a bad choice for its intended use case, which is application configuration files of moderate complexity. They do mean you should reach for a different format when your data structure outgrows what TOML handles well.

Converting Between TOML and Other Formats

If you are migrating a project from YAML to TOML, or need to convert configuration data between formats for tooling reasons, there are a few practical paths. For programmatic conversion in Python, the `tomllib` (read) and `tomli-w` (write) libraries handle TOML, while `PyYAML` handles YAML. You can read a YAML file into a Python dictionary with `yaml.safe_load()` and write it back out as TOML with `tomli_w.dumps()`. This works well for flat to moderately nested structures. Deep nesting, YAML anchors, and multi-document YAML files require manual cleanup after conversion. For JSON to TOML conversion, the mapping is more direct since both formats share explicit typing. A JSON integer stays an integer in TOML. A JSON boolean stays a boolean. The main structural difference is that JSON arrays of objects become TOML arrays of tables (`[[table]]` syntax), which requires a conversion step rather than a direct type mapping. CocoConvert supports TOML conversion as part of its format library. You can upload a JSON or YAML file and convert it to TOML through the web interface — select your source file, choose TOML as the output format, and the converter handles the type mapping automatically. For straightforward configuration files, this works well. For files with YAML-specific features like anchors, multi-document streams, or deeply nested structures beyond four levels, you will likely need to review the output manually before using it in production. CocoConvert will flag conversion warnings when it detects features that do not have a clean TOML equivalent, but it cannot make architectural decisions about how to restructure your data. If you are converting in bulk — say, migrating a repository of 40 YAML configuration files — the CocoConvert API accepts batch requests, which is more practical than uploading files one at a time through the browser.

Should You Switch Your Project to TOML?

The practical answer depends on what you are configuring and what your toolchain already expects. If you are starting a new Python project in 2025, use `pyproject.toml`. The ecosystem has largely standardized on it, and fighting that convention creates friction with every tool you add later. Running `pip install ruff` and then having to configure it in a separate `ruff.toml` instead of the standard `[tool.ruff]` section of your existing `pyproject.toml` is unnecessary work. If you are writing a Rust project, you have no choice — Cargo uses TOML and that is a good thing. The consistency across the Rust ecosystem is part of what makes the tooling feel coherent. If you are maintaining an existing project with YAML configuration, migration is only worth it if the YAML is actively causing problems. Switching formats has a cost: updating documentation, updating any tooling that reads the config, and the conversion itself. If your `config.yaml` works reliably and your team understands it, the benefit of switching to TOML may not justify that cost. For Kubernetes and CI/CD configurations — GitHub Actions, GitLab CI, CircleCI — YAML is the required format and there is no choice to be made. TOML is not trying to replace YAML in every context, and its creator never claimed it should. The clearest signal that TOML is the right choice is when you find yourself adding comments to a YAML file explaining what type a value is supposed to be, or when you have caught a bug caused by implicit type coercion. Those are symptoms of a format mismatch between what your data needs and what YAML provides. TOML eliminates that entire category of problem by making types explicit and mandatory from the start.