TOML and INI files

Sections, dotted keys, typed values and arrays without ambiguity, plus why TOML replaced INI for tooling config and how to migrate a file between them.

TOML keeps the types

TOML is a config format with the ergonomics of INI and the type safety of JSON. Every value has an unambiguous type, and there is exactly one way to write it — which is exactly what YAML lacks.

title = "example"
version = "1.10"              # string, always
port = 8080                    # integer
ratio = 0.75                   # float
enabled = true                 # boolean
date = 2026-09-18T10:00:00Z    # native datetime

tags = ["a", "b"]              # array
[server]
host = "0.0.0.0"
timeout = 30

[server.limits]
max_conn = 100                 # nested table

[database]
host = "localhost"
# dotted key writes into the table above
database.port = 5432
  • Order of keys is preserved, so the file you write is the file you read.
  • Arrays of tables use [[name]] and are the idiomatic way to express a list of records.
  • Comments run to the end of the line with #.

INI is a family, not a standard

; classic ini
[server]
host = 0.0.0.0
port = 8080

[database]
host = localhost
port = 5432
FeatureINITOML
Value typesAll strings; the reader guessesExplicit int, float, bool, date, array
NestingOne level of sectionsArbitrary tables and arrays of tables
DuplicatesOften last-wins, silentlyDefined behaviour per table
SpecificationNone — every parser differsVersioned spec with a test suite
InterpolationSome dialects support it (%s or brace syntax)None; compose in code

INI's flexibility is the problem: configparser, Windows GetPrivateProfileString and older tools disagree about quoting, comments and duplicate keys. Migrate to TOML when a real tool consumes the file.

Reading them in code

import tomllib          # read-only, Python 3.11+
import configparser

with open("app.toml", "rb") as f:
    cfg = tomllib.load(f)          # must be opened in binary mode
print(cfg["server"]["port"])       # already an int

parser = configparser.ConfigParser()
parser.read("legacy.ini")
port = parser.getint("server", "port")   # you must coerce by hand
⚠️
In INI, boolean parsing is a known trap: configparser.getboolean accepts only 1/yes/true/on and their negatives, so enabled = enable raises a ValueError at runtime. Validate config at startup rather than at first use.

FAQ

Should config be TOML or YAML?
TOML when a human edits a small, typed file (tool config, project metadata). YAML when the structure is deep and tool-shaped, and JSON when it is generated.
Can TOML express an array of records?
Yes. Use [[server]] blocks; each occurrence appends one record to the server array, which maps cleanly to a list of objects.

YAML for configuration Choosing a format: size, speed, readability and tooling

Last refreshed 2026-09-18.