Cheatsheet

TOML Syntax Cheatsheet

TOML is a configuration format where every document maps to one hash table of keys and typed values, and it is what pyproject.toml and Cargo.toml files are written in. This sheet is for developers who read or write TOML config by hand. The confusion it clears up: [table] and [[array of tables]] look alike but build different structures, and a key can be defined only once across both dotted keys and headers. Code below was parsed with Python 3.11 tomllib, which implements TOML 1.0.0.

Quick reference

Basics

Rule Detail
File encoding UTF-8 only
Case Keys and values are case-sensitive, so `true` works and `True` is an error
Whitespace Tab or space, ignored around keys, values and `=`
Line ending LF or CRLF
Comment `#` to end of line, except inside a string
One pair per line `a = 1 b = 2` is an error
File extension `.toml`
MIME type `application/toml`

Keys

Style Example Notes
Bare `site_name = "x"` Only `A-Za-z0-9_-`, and digits-only keys like `1234` are strings
Quoted `"127.0.0.1" = "x"` Basic or literal string quotes allow any characters
Dotted `physical.color = "orange"` Creates a table `physical` with key `color`
Empty bare `= "x"` Invalid
Empty quoted `"" = "x"` Valid but discouraged

Value types

Type Example Python type from `tomllib`
String `"text"`, `'text'`, `"""text"""`, `'''text'''` `str`
Integer `42`, `+99`, `-17`, `1_000` `int`
Hex, octal, binary `0xDEADBEEF`, `0o755`, `0b1101` `int`
Float `3.1415`, `5e+22`, `-0.01` `float`
Special floats `inf`, `-inf`, `nan` `float`
Boolean `true`, `false` `bool`
Offset date-time `2026-10-05T09:30:00+02:00` `datetime` with a zone
Local date-time `2026-10-05T09:30:00` `datetime` without a zone
Local date `2026-10-05` `date`
Local time `09:30:00` `time`
Array `[1, 2, 3]` `list`
Inline table `{ x = 1, y = 2 }` `dict`

There is no null type. A key with no value, key =, is invalid, so omit the key instead.

Strings

Kind Delimiter Escapes Newlines
Basic `"..."` Yes No
Multi-line basic `"""..."""` Yes Yes, and the first newline is trimmed
Literal `'...'` None No
Multi-line literal `'''...'''` None Yes, and the first newline is trimmed

Escapes in basic strings are \b, \t, \n, \f, \r, \", \\, \uXXXX and \UXXXXXXXX. Any other backslash sequence is an error. A line-ending backslash inside a multi-line basic string removes the newline and the whitespace after it.

Numbers

Rule Valid Invalid
Underscores need a digit on each side `1_000`, `224_617.445_991_228` `_1`, `1_`
No leading zeros in decimal integers `0`, `-0`, `+0` `007`
Prefixed forms allow leading zeros `0o01234567` `+0xFF`
Decimal point needs digits on both sides `0.7`, `7.0` `.7`, `7.`, `3.e+20`
Special floats are lowercase `inf`, `nan` `Inf`, `NaN`
Integer range -2^63 to 2^63-1 should work A parser must error if it cannot hold the value

Tables

Syntax Meaning
`[server]` Table named `server`, up to the next header
`[a.b.c]` Nested tables, parent tables are created implicitly
`[ a . b ]` Same as `[a.b]`, spaces are ignored
`[[products]]` Append a new table to the array `products`
`[products.dims]` Sub-table of the most recent `[[products]]` element
`a = { x = 1 }` Inline table, fully closed, nothing can be added later

TOML 1.0 versus 1.1

Feature 1.0.0 (2021-01-11) 1.1.0 (2025-12-18)
Inline table over several lines Not allowed Allowed
Trailing comma in an inline table Not allowed Allowed
Seconds in a time, `14:15` Required Optional
`\xHH` escape for code points up to 255 No Yes
`\e` escape for the escape character No Yes

Common patterns

Write and read a typical config

import tomllib

cfg = tomllib.loads('''
title = "ZipKit"
port = 8_080
mask = 0o755
ratio = 6.626e-34
debug = false
tags = ["a", 1, 2.5]

[database]
url = "postgres://localhost/app"
''')
print(cfg)
print(cfg["port"] + 1, type(cfg["mask"]).__name__, cfg["mask"])
{'title': 'ZipKit', 'port': 8080, 'mask': 493, 'ratio': 6.626e-34, 'debug': False, 'tags': ['a', 1, 2.5], 'database': {'url': 'postgres://localhost/app'}}
8081 int 493

Keys before the first header belong to the root table. Octal 0o755 is the integer 493, which is handy for Unix file modes. Arrays can mix types in 1.0.

Group settings with dotted keys, headers or inline tables

import tomllib

dotted = tomllib.loads('''
server.host = "localhost"
server.port = 8080
''')
header = tomllib.loads('''
[server]
host = "localhost"
port = 8080
''')
inline = tomllib.loads('server = { host = "localhost", port = 8080 }')
print(dotted)
print(dotted == header == inline)
{'server': {'host': 'localhost', 'port': 8080}}
True

All three forms produce the same dictionary. Use dotted keys for one or two nested values, headers for a group of settings and inline tables for short records inside arrays.

Build a list of records with arrays of tables

import tomllib

doc = '''
[[products]]
name = "Hammer"
sku = 738594937

[[products]]

[[products]]
name = "Nail"
sku = 284758393

[products.dims]
mm = 40
'''
for p in tomllib.loads(doc)["products"]:
    print(p)
{'name': 'Hammer', 'sku': 738594937}
{}
{'name': 'Nail', 'sku': 284758393, 'dims': {'mm': 40}}

Each [[products]] header starts a new element, and a later [products.dims] attaches to the most recent one. The empty second header creates an empty table.

Use date and time values

import tomllib

doc = '''
released = 2026-10-05
at = 2026-10-05T09:30:00+02:00
utc = 2026-10-05T07:30:00Z
local = 2026-10-05T09:30:00
clock = 09:30:00
'''
for k, v in tomllib.loads(doc).items():
    print(k, type(v).__name__, repr(v))
released date datetime.date(2026, 10, 5)
at datetime datetime.datetime(2026, 10, 5, 9, 30, tzinfo=datetime.timezone(datetime.timedelta(seconds=7200)))
utc datetime datetime.datetime(2026, 10, 5, 7, 30, tzinfo=datetime.timezone.utc)
local datetime datetime.datetime(2026, 10, 5, 9, 30)
clock time datetime.time(9, 30)

Dates are real types, not strings, and they follow RFC 3339. A date-time without an offset is a local value that cannot be turned into an instant without outside information. A space may replace the T.

Write long text, paths and regexes

import tomllib

doc = '''basic = "tab\\there \\u00e9"
literal = 'C:\\Users\\app'
multi = """
Roses are red
Violets are blue"""
folded = """\\
  The quick brown \\
  fox jumps."""
quote = """She said "hi"."""
'''
print(tomllib.loads(doc))
{'basic': 'tab\there é', 'literal': 'C:\\Users\\app', 'multi': 'Roses are red\nViolets are blue', 'folded': 'The quick brown fox jumps.', 'quote': 'She said "hi".'}

The Python source doubles the backslashes, so the TOML text itself has single ones. Use literal strings with single quotes for Windows paths and regular expressions, because nothing inside them is escaped.

Read a file in Python

import tomllib
with open("pp.toml", "rb") as f:
    print(tomllib.load(f)["project"]["name"])
demo

Open the file in binary mode. Passing a text-mode file raises a TypeError that says the file must be opened in binary mode. tomllib only reads TOML and has no writer, so use a third-party package to write it.

Catch syntax errors with a line number

import tomllib

for bad in ['[fruit]\napple = "red"\n[fruit]\n', 'a = 1\na = 2\n', 'n = 007\n', 'name = Tom\n']:
    try:
        tomllib.loads(bad)
    except tomllib.TOMLDecodeError as e:
        print(repr(bad), '->', e)
'[fruit]\napple = "red"\n[fruit]\n' -> Cannot declare ('fruit',) twice (at line 3, column 7)
'a = 1\na = 2\n' -> Cannot overwrite a value (at line 2, column 6)
'n = 007\n' -> Expected newline or end of document after a statement (at line 1, column 6)
'name = Tom\n' -> Invalid value (at line 1, column 8)

Catch TOMLDecodeError at startup and print the message, which includes the line and column. Failing fast on a bad config is better than running with defaults. The messages are from Python 3.11, and other parsers word them differently.

Pitfalls

  • Defining a table twice: [fruit] followed later by another [fruit] fails with Cannot declare ('fruit',) twice (at line 3, column 7). Merge the keys under one header.
  • Repeating a key: a = 1 then a = 2 fails with Cannot overwrite a value (at line 2, column 6). PyYAML keeps the last value silently, but TOML rejects it.
  • Leading zeros and bare strings: n = 007 and name = Tom both fail. Write n = 7 and name = "Tom", because every string value needs quotes.
  • Expecting null: TOML has no null, and null is parsed as an invalid value. Leave the key out and let the application supply a default.
  • Extending an inline table: after type = { name = "Nail" }, the line type.edible = false fails with Cannot mutate immutable namespace. Put everything inside the braces or use a normal table.
  • Appending to a static array: fruits = [] followed by [[fruits]] is an error, because [[...]] only extends arrays of tables.
  • Using 1.1 syntax on a 1.0 parser: t = 14:15, \e and multi-line inline tables fail in Python 3.11 tomllib, which implements TOML 1.0.0. Check the spec version your parser supports before you use newer syntax.
  • Dots inside bare keys: a.b = 2 makes table a, while "a.b" = 1 makes one key named a.b. Quote any key that really contains a dot, such as a domain name.
  • Trailing comma in a one-line inline table: p = {x = 1, y = 2,} is invalid in 1.0 and errors with Invalid initial character for a key part.

Related ZipKit tools

Related cheatsheets