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:

HeaderCreatesJSON
[products] oncea table{"products": {...}}
[[products]] oncean array with one table{"products": [{...}]}
[[products]] three timesan 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

AttemptError
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

FileArray 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.tomlnone, 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.