TOML Cheatsheet

Tables

Use this TOML reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

A header owns everything until the next header

[name] starts a table. Every key = value after it belongs to that table until the next [header] or [[header]] line or end of file.

title = "top level"

[server]
host = "localhost"
port = 8080

[database]
url = "postgres://localhost/app"
pool = 10

parses to:

{"title": "top level",
 "server": {"host": "localhost", "port": 8080},
 "database": {"url": "postgres://localhost/app", "pool": 10}}

Note that title is at the root because it appears before any header. Anything after [server] is inside server, with no way to get back to the root except a dotted key from a later header. In practice: put every root-level key at the top of the file, before the first [.

Nesting

Dots in a header nest tables. Whitespace around the dots is allowed and ignored.

[a.b.c]
x = 1

[ a . b . d ]
y = 2

gives {"a": {"b": {"c": {"x": 1}, "d": {"y": 2}}}}. Header names follow the key rules exactly, so quoting works for odd segments:

["ʞ.ǝʎ"]
x = 1

[tool."ruff-extra"]
y = 2

[servers."192.168.1.1"]
role = "primary"

["ʞ.ǝʎ"] is one table whose name contains a dot. [servers."192.168.1.1"] is servers containing a table named 192.168.1.1.

Super-tables are created implicitly

You never have to declare a parent. Writing [a.b.c] creates a and a.b on the way.

[fruit.apple.texture]
smooth = true

gives {"fruit": {"apple": {"texture": {"smooth": true}}}}. And an implicitly created parent may be defined later, which is legal and common:

[x.y.z.w]
a = 1

[x]
b = 2

gives {"x": {"y": {"z": {"w": {"a": 1}}}, "b": 2}}. Ordering is free, so a file may declare children before parents. Most tools write parents first because it reads better, not because the spec requires it.

You cannot redefine a table

This is the rule that separates TOML from YAML, and the one that produces most error messages.

AttemptResult
[a] twiceerror, Cannot declare ('a',) twice
[a.b] twiceerror, Cannot declare ('a', 'b') twice
[a.b] then [a]fine, a was only implicit
a.b = 1 in [t] then [t.a]error, a is already a defined table
a = 1 then [a]error, Cannot overwrite a value
p = [] then [[p]]error, Cannot mutate immutable namespace
[[p]] then [p]error, Cannot declare ('p',) twice
[fruit]
apple = "red"

[fruit]
banana = "yellow"

fails with TOMLDecodeError: Cannot declare ('fruit',) twice (at line 4, column 7). The fix is to merge the two blocks, or to nest: [fruit.apple] and [fruit.banana].

The practical consequence for generated files is that a tool appending a [tool.something] block must first check whether one already exists. tomlkit handles this. A naive open(f, "a") does not.

Dotted keys versus headers

A dotted key defines the same structure as a header. Pick one style per table and stay with it, because mixing them is where the redefinition errors come from.

# these two files are identical
[tool.ruff]
line-length = 100
target-version = "py312"
tool.ruff.line-length = 100
tool.ruff.target-version = "py312"

Both give {"tool": {"ruff": {"line-length": 100, "target-version": "py312"}}}. The header form wins for more than two keys, and the dotted form wins for a single setting you do not want a whole block for.

A dotted key inside a table defines a sub-table you then own. After

[t]
a.b = 1

[t.a] is a redefinition and fails with Cannot declare ('t', 'a') twice. This is the trap: a one-line tool.ruff.lint.select = ["E"] at the top of a pyproject.toml makes a later [tool.ruff.lint] block illegal, with Cannot declare ('tool', 'ruff', 'lint') twice.

Empty tables and ordering

[empty]

[also-empty]
# just a comment

[filled]
x = 1

gives {"empty": {}, "also-empty": {}, "filled": {"x": 1}}. A header with nothing under it is a real, empty table, which is how you express "this feature is enabled with all defaults".

Key order within a table is not semantically meaningful, and most parsers do not preserve it (Python's tomllib returns a plain dict, so insertion order happens to survive). Table order in the file is likewise free. Do not encode meaning in either.