TOML Explained: Syntax, Types and Converting to JSON and YAML

TOML 1.0 from the ground up: tables, inline tables, arrays of tables, the four date and time types, why there is no null, common errors, and how values map to JSON and YAML.

ยท 11 min read

Illustration for the TOML to JSON and YAML Converter guide

If you write Rust you edit Cargo.toml, if you package Python you edit pyproject.toml, and if you deploy static sites you may have a netlify.toml or a Hugo config.toml. TOML has become the default format for tool configuration because it is easy to read, has comments, and maps cleanly to a dictionary. This guide covers the syntax, the type system, the mistakes the parser will catch, and what happens when you convert TOML to JSON or YAML. To experiment, paste any example into the TOML to JSON and YAML Converter.

Where TOML came from

TOML, short for Tom's Obvious, Minimal Language, was created by Tom Preston-Werner in 2013. The goal was a configuration format that is "obvious" to read and maps unambiguously to a hash table, avoiding the surprises of YAML (where no can become a boolean and indentation carries meaning) and the noise of JSON (no comments, quotes on every key, no trailing commas). Version 1.0.0 of the specification was released in January 2021 and is the version current parsers implement.

Adoption followed the tools. Rust's package manager Cargo used TOML from the start. Python standardised pyproject.toml for build configuration in PEP 518 and project metadata in PEP 621, and Python 3.11 added tomllib to the standard library for reading it. Hugo, Netlify, Fly.io, Ruff, uv and Black all read TOML configuration.

Keys, values and tables

A TOML document is a table: a set of key/value pairs.

title = "TOML Example"  # a comment starts with a hash
port = 8080
debug = false

Keys can be bare (letters, digits, - and _), quoted ("key with spaces" = 1) or dotted (server.port = 8080, which means "the port key inside the server table").

A header in square brackets starts a new table, and everything below it belongs to that table until the next header:

[database]
host = "localhost"
ports = [8000, 8001]

[servers.alpha]
ip = "10.0.0.1"

[servers.alpha] creates the table servers (implicitly) and the table alpha inside it. Indentation is allowed but means nothing.

Inline tables put a small table on one line, which Cargo uses for dependencies with options:

serde = { version = "1.0", features = ["derive"] }

Inline tables must stay on one line and cannot be extended later in the file.

Arrays of tables use double brackets. Each [[name]] header appends a new table to an array:

[[bin]]
name = "server"
path = "src/main.rs"

[[bin]]
name = "cli"
path = "src/cli.rs"

In JSON this is "bin": [{"name": "server", ...}, {"name": "cli", ...}].

Side-by-side mapping of a TOML file to JSON: the package table becomes a nested object, the inline table for serde becomes an object with version and features, and two bin array-of-tables sections become an array of two objects
Side-by-side mapping of a TOML file to JSON: the package table becomes a nested object, the inline table for serde becomes an object with version and features, and two bin array-of-tables sections become an array of two objects

The type system

TOML is strongly typed, which is one of its advantages over YAML.

TypeExampleNotes
String"basic\n", 'literal', """multi-line"""Literal strings ignore backslashes, handy for Windows paths and regexes
Integer42, -17, 1_000_000, 0xff, 0o755, 0b101064-bit signed range
Float3.14, 6.626e-34, inf, nanIEEE 754 binary64
Booleantrue, falseLower case only
Offset date-time1979-05-27T07:32:00-08:00RFC 3339, an instant in time
Local date-time1979-05-27T07:32:00No time zone
Local date1979-05-27
Local time07:32:00
Array[1, 2, 3], ["a", 1]Mixed types allowed since 1.0
Table[name] or { a = 1 }

Two things are missing on purpose. There is no null: a value that is not set is simply absent. And there are no references or anchors like YAML's & and *.

Errors the parser catches

TOML parsers are strict, which is good for configuration: a typo fails loudly instead of producing a surprising value. Common errors:

  • Defining a key twice. title = "a" then title = "b" is an error, not "last one wins". Our converter reports it as "trying to redefine an already defined table or value" at line 2, column 1.
  • Defining a table twice. Two [database] headers are an error. So is a [database] header after you already set database.host = ... with a dotted key at the top level.
  • Extending an inline table. After owner = { name = "Tom" }, a later [owner] or owner.age = 1 is an error.
  • Unquoted strings. name = Tom is invalid; strings always need quotes.
  • Wrong-case booleans. True and TRUE are invalid.
  • Integers out of range. Values beyond the 64-bit signed range are rejected.
  • Bad dates. 2023-02-30 is rejected as a date.

The converter shows each error with its line and column, and you can choose TOML as both input and output to use it purely as a validator.

Converting TOML to JSON and YAML

Most TOML maps directly to JSON and YAML: tables become objects, arrays stay arrays, strings, numbers and booleans keep their types. Three areas need care.

Dates and times. JSON has no date type, so TOML dates become strings in RFC 3339 form: "1979-05-27T07:32:00-08:00", "1979-05-27" or "07:32:00". YAML output uses the same strings. On the way back, those strings stay strings unless you tick the option to turn date-shaped strings into native TOML dates. That is off by default because a string that happens to look like a date may be meant as text (a version label, for example).

Large integers. TOML integers are 64-bit, but JavaScript numbers are exact only up to 2^53 (9,007,199,254,740,992). The converter parses big integers exactly and writes them as exact digits in JSON, then warns you, because a JavaScript program reading that JSON will round them. Snowflake IDs and nanosecond timestamps are the usual culprits.

inf and nan. TOML floats can be inf, -inf or nan. YAML has .inf and .nan, but JSON has no way to write them, so the JSON output uses the strings "inf" and "nan" and lists them in the warnings.

Converting JSON and YAML to TOML

The other direction has its own edges.

  • The top level must be an object. A TOML document is a table, so a JSON array at the top level cannot be converted. Wrap it: {"items": [...]} becomes an array of tables written as [[items]].
  • Nulls are dropped. Because TOML has no null, keys with null values are removed and listed in the warnings. Nulls inside arrays are removed too.
  • Floats with no fraction become integers. JSON parsers read 1.0 as the number 1, so it is written to TOML as the integer 1. Strictly typed readers, such as Rust's serde with an f64 field, may then reject it. Check fields that must be floats.
  • Comments cannot be created, and comments in a TOML source do not survive a round trip through JSON or YAML, because they are not part of the data.
  • YAML anchors are expanded. A YAML alias becomes a full copy of the referenced value in TOML.

The output follows common TOML style: plain keys first, then [table] sections for nested objects and [[array]] sections for arrays of objects.

TOML, YAML or JSON?

TOMLYAMLJSON
CommentsYesYesNo
NullNoYesYes
DatesNativeAs strings in YAML 1.2 core schemaNo
Deep nestingGets verboseNaturalNatural
Whitespace-sensitiveNoYesNo
Typical useTool and package configCI pipelines, KubernetesAPIs, data exchange

TOML shines for flat-ish configuration that humans edit. Deeply nested data, such as a Kubernetes manifest, reads better in YAML, and data exchanged between programs belongs in JSON. Our guide on JSON and YAML compares those two in more depth.

A quick reference for Cargo.toml and pyproject.toml

[package]  # Cargo.toml
name = "demo"
version = "0.1.0"
edition = "2021"

[dependencies]
serde = { version = "1.0", features = ["derive"] }

[profile.release]
lto = true
[project]  # pyproject.toml
name = "demo"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["requests>=2.32"]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

Both files are ordinary TOML, so the converter can show them as JSON when you want to process them with a tool such as jq, or validate them before a release.

Related tools: JSON to YAML Converter, YAML Beautifier and the JSON Schema Validator for checking the converted data.

Related searches

  • toml format explained
  • toml array of tables
  • toml vs yaml vs json
  • toml inline table syntax
  • why toml has no null
  • cargo.toml syntax
  • pyproject.toml format
  • toml datetime types

Open the TOML to JSON and YAML Converter