TOML Cheatsheet
Array of Tables
Use this TOML reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
[[x]] appends, [x] defines
Double brackets are the only way TOML expresses a list of table-shaped things.
Each [[name]] header appends a new element to the array name.
[[products]] name = "Hammer" sku = 738594937 [[products]] [[products]] name = "Nail" sku = 284758393 color = "gray"
The exact JSON equivalent, verified with tomllib:
{"products": [
{"name": "Hammer", "sku": 738594937},
{},
{"name": "Nail", "sku": 284758393, "color": "gray"}
]}Three headers, three elements. The middle one is an empty object, not skipped and not null. The two forms side by side:
| Header | Creates | JSON |
|---|---|---|
[products] once | a table | {"products": {...}} |
[[products]] once | an array with one table | {"products": [{...}]} |
[[products]] three times | an array with three tables | {"products": [{}, {}, {}]} |
A single [[products]] still produces an array of length one. That trips up
readers that expect a bare object when there is only one entry.
Sub-tables attach to the most recent element
Inside an array of tables, a [name.sub] header or a nested [[name.sub]] binds
to the last element defined so far.
[[fruit]] name = "apple" [fruit.physical] color = "red" shape = "round" [[fruit.variety]] name = "red delicious" [[fruit.variety]] name = "granny smith" [[fruit]] name = "banana" [[fruit.variety]] name = "plantain"
{"fruit": [
{"name": "apple",
"physical": {"color": "red", "shape": "round"},
"variety": [{"name": "red delicious"},
{"name": "granny smith"}]},
{"name": "banana",
"variety": [{"name": "plantain"}]}
]}That output is tomllib's, not a sketch. physical is a plain table on the apple,
variety is a nested array of tables, and the [[fruit.variety]] after the second
[[fruit]] lands on the banana. Order in the file is the only thing deciding
which parent an entry attaches to, so never reorder these blocks casually.
The errors
| Attempt | Error |
|---|---|
p = [] then [[p]] | Cannot mutate immutable namespace ('p',) |
[[p]] then [p] | Cannot declare ('p',) twice |
[p] then [[p]] | Cannot overwrite a value |
p = {} then [[p.q]] | Cannot mutate immutable namespace ('p', 'q') |
[[p.q]] with no [[p]] | fine, p is created implicitly as a table |
The first row is the common one. An array literal and an array of tables are
different things, and you cannot start one and continue with the other. If you
declared dependencies = [] you must keep using inline tables in that list.
# implicit parent, this is legal [[fruit.variety]] name = "plantain"
gives {"fruit": {"variety": [{"name": "plantain"}]}}. Note fruit is a plain
table here, not an array, because nothing declared [[fruit]].
Array of tables versus an array of inline tables
Both produce a list of objects. Same data model, different ergonomics.
# array of tables, one block per entry [[server]] host = "a.example.com" port = 8080 tags = ["primary"] [[server]] host = "b.example.com" port = 8080 tags = ["replica"]
# array of inline tables, one line per entry server = [ {host = "a.example.com", port = 8080, tags = ["primary"]}, {host = "b.example.com", port = 8080, tags = ["replica"]}, ]
Both parse to the same thing. Use [[...]] when entries have several fields,
comments, or sub-tables. Use inline tables when the entries are short and uniform
and you want them to read like rows. An inline table is immutable once written,
so the second form cannot be extended by a later header.
Where you actually meet this
| File | Array of tables |
|---|---|
Cargo.toml | [[bin]], [[test]], [[bench]], [[example]] |
pyproject.toml | [[tool.mypy.overrides]] |
netlify.toml | [[redirects]], [[headers]], [[plugins]] |
fly.toml | [[services]], [[services.ports]], [[mounts]] |
rust-toolchain.toml | none, single tables only |
[[tool.mypy.overrides]] module = ["yaml.*", "tomlkit.*"] ignore_missing_imports = true [[tool.mypy.overrides]] module = "legacy.*" disallow_untyped_defs = false
parses to a two-element tool.mypy.overrides array. Note the first module is a
list and the second is a string, which mypy accepts and TOML does not care about,
since arrays may be heterogeneous and sibling tables need not match shapes.