TOML Configuration: Syntax Guide, Validation & Best Practices [2026]
TOML has quietly become one of the most widely used configuration file formats in the software industry without receiving the fanfare of YAML or JSON. If you have written a Rust project, you have written TOML — every Cargo.toml file is TOML. If you have configured a modern Python package, you have likely encountered pyproject.toml. If you have deployed to Netlify, your netlify.toml is TOML.
Despite this ubiquity, TOML is less documented and less discussed than its competitors. This guide explains the language from scratch: what TOML is, how its syntax works, how it compares to YAML and JSON, and how to validate and work with TOML effectively in real projects.
1. What Is TOML?
TOML stands for Tom's Obvious, Minimal Language. It was created by Tom Preston-Werner (co-founder of GitHub) in 2013 as a configuration file format with clearer semantics than YAML and better readability than JSON. The specification reached version 1.0.0 in 2021, providing a stable, unambiguous foundation that has driven its adoption.
TOML's core design goals are:
- Human-readable: Configuration should be easy to read and write without needing to memorize complex rules
- Unambiguous: Every TOML file has exactly one valid interpretation — no implicit type coercions, no parser-dependent behavior
- Maps to a hash table: TOML is designed to parse directly into a dictionary or hash map, not a tree of arbitrary nesting like YAML
- Minimal: The specification is intentionally small — TOML does not try to be a full data serialization format for all use cases
TOML files use the .toml extension and are encoded in UTF-8. Comments use the # character (same as YAML and Python).
2. TOML vs YAML vs JSON
| Feature | TOML | YAML | JSON |
|---|---|---|---|
| Comments | Yes (#) |
Yes (#) |
No |
| Readability | High | High | Moderate |
| Implicit type coercion | None | Yes (Norway problem) | None |
| Date/time type | Native (RFC 3339) | Native (often problematic) | No (string only) |
| Whitespace significance | No (except newlines) | Yes (indentation defines structure) | No |
| Anchors / reuse | No | Yes | No |
| Deep nesting | Awkward | Natural | Natural (but verbose) |
| Spec stability | v1.0 (2021), stable | YAML 1.2 (2009), many parsers use 1.1 | RFC 8259, very stable |
| API use | Rare | Rare | Universal |
| Config file use | Rust, Python, Netlify, Hugo | Kubernetes, Docker, GitHub Actions | Node.js, TypeScript, VS Code |
TOML occupies a specific niche: it is better than JSON for configuration (it has comments and native datetime) and safer than YAML (no implicit type coercion). Its limitation is that it does not handle deeply nested or complex structures as elegantly — which is why Kubernetes uses YAML rather than TOML.
3. TOML Syntax Basics
Key-Value Pairs
The fundamental unit of TOML is the key-value pair: a key, an equals sign, and a value on a single line. Keys can be bare (unquoted alphanumeric), quoted with double quotes, or quoted with single quotes:
# Bare key
name = "Alice"
age = 30
active = true
# Quoted key (required for keys with spaces or special chars)
"full name" = "Alice Smith"
"app.version" = "1.0.0"
# Dotted key — creates nested structure inline
server.host = "localhost"
server.port = 8080
Strings
TOML has four string types:
# Basic string — supports escape sequences (\n, \t, \", \\)
message = "Hello, World!\nSecond line"
# Literal string — no escape processing (use for regex, Windows paths)
path = 'C:\Users\Alice\Documents'
# Multi-line basic string — preserves newlines, supports escapes
description = """
This is a
multi-line string.
"""
# Multi-line literal string — no escapes, preserves everything
raw = '''
All \backslashes\ are literal here.
'''
Numbers
integer = 42
negative = -17
hex_value = 0xDEADBEEF
octal = 0o755
binary = 0b11010110
float = 3.14159
scientific = 5e+22
infinity = inf
nan = nan
4. Tables and Arrays of Tables
Tables are TOML's mechanism for grouping related key-value pairs. They are equivalent to nested objects in JSON or mappings in YAML.
Standard Tables
[database]
host = "localhost"
port = 5432
name = "myapp"
[server]
host = "0.0.0.0"
port = 8080
debug = false
This is equivalent to the JSON:
{
"database": { "host": "localhost", "port": 5432, "name": "myapp" },
"server": { "host": "0.0.0.0", "port": 8080, "debug": false }
}
Nested Tables
Dotted notation creates nested tables without requiring a separate header for each level:
[database.primary]
host = "db.prod.example.com"
port = 5432
[database.replica]
host = "db.replica.example.com"
port = 5432
Arrays of Tables
Double brackets [[table]] define an array of tables — equivalent to a JSON array of objects. This is one of TOML's most important and most confusing features:
[[server]]
name = "web-1"
ip = "10.0.0.1"
[[server]]
name = "web-2"
ip = "10.0.0.2"
[[server]]
name = "web-3"
ip = "10.0.0.3"
This creates an array server containing three objects. The equivalent JSON:
{
"server": [
{"name": "web-1", "ip": "10.0.0.1"},
{"name": "web-2", "ip": "10.0.0.2"},
{"name": "web-3", "ip": "10.0.0.3"}
]
}
Convert Between TOML, YAML, and JSON
Working across different config formats? The SnapUtils YAML to JSON converter handles YAML and JSON conversions instantly. Pair it with a TOML library to move between all three formats.
Open YAML to JSON Converter5. TOML Data Types
TOML has a richer native type system than JSON, particularly for dates and times:
| Type | Example | Notes |
|---|---|---|
| String | "hello" |
Four variants (basic, literal, multi-line) |
| Integer | 42, -17, 0xFF |
Supports hex, octal, binary notation |
| Float | 3.14, 1e10, inf |
IEEE 754 double precision |
| Boolean | true, false |
Lowercase only; no coercion from yes/no/on/off |
| Offset DateTime | 2026-04-19T09:00:00Z |
RFC 3339 with timezone offset |
| Local DateTime | 2026-04-19T09:00:00 |
No timezone (local time) |
| Local Date | 2026-04-19 |
Date only, no time |
| Local Time | 09:00:00 |
Time only, no date |
| Array | [1, 2, 3] |
All elements must be same type (or mixed in TOML 1.0) |
| Inline Table | {x = 1, y = 2} |
Single-line table; must be complete, no modification allowed |
The native datetime types are a significant advantage over both YAML (where datetime parsing varies by parser) and JSON (which has no datetime type at all). In TOML, created_at = 2026-04-19T09:00:00Z is unambiguously a datetime, not a string that some parser might or might not convert.
6. Real-World TOML Examples
Cargo.toml (Rust)
[package]
name = "my-app"
version = "0.1.0"
edition = "2021"
description = "A sample Rust application"
authors = ["Alice Smith <alice@example.com>"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1.0", features = ["full"] }
anyhow = "1.0"
[dev-dependencies]
mockall = "0.11"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1
pyproject.toml (Python)
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-python-app"
version = "0.1.0"
description = "A Python application"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.100.0",
"pydantic>=2.0",
"sqlalchemy>=2.0",
]
[project.optional-dependencies]
dev = [
"pytest",
"ruff",
"mypy",
]
[tool.ruff]
line-length = 100
select = ["E", "F", "W"]
netlify.toml
[build]
command = "npm run build"
publish = "dist"
[build.environment]
NODE_VERSION = "20"
[[redirects]]
from = "/api/*"
to = "https://api.example.com/:splat"
status = 200
force = true
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-Content-Type-Options = "nosniff"
7. TOML Validation in CI/CD
Validating TOML files in CI prevents broken configuration from reaching production. Unlike YAML (where a syntactically valid file can still produce surprising behavior), a valid TOML file always parses to the expected structure — validation primarily catches syntax errors.
Using taplo (Recommended CLI)
Taplo is the most comprehensive TOML toolchain, providing validation, formatting, and schema enforcement:
# Install taplo
cargo install taplo-cli
# Validate a single file
taplo check Cargo.toml
# Validate all TOML files in a directory
taplo check '**/*.toml'
# Format TOML files (auto-fix whitespace, sorting)
taplo fmt Cargo.toml
GitHub Actions Example
name: Validate TOML
on: [push, pull_request]
jobs:
validate-toml:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install taplo
run: cargo install taplo-cli --locked
- name: Validate TOML files
run: taplo check '**/*.toml'
Python (Built-in, Python 3.11+)
import tomllib
import sys
def validate_toml(path):
try:
with open(path, "rb") as f:
tomllib.load(f)
print(f"Valid: {path}")
except tomllib.TOMLDecodeError as e:
print(f"Invalid: {path}: {e}")
sys.exit(1)
validate_toml("pyproject.toml")
8. Common TOML Pitfalls
Redefining a Key
TOML is strict: you cannot define the same key twice. This is a common mistake when migrating from INI files or when multiple team members edit the same file:
# INVALID: duplicate key
port = 8080
port = 9090 # Error: key 'port' already defined
Similarly, you cannot define a table header after a dotted key has already defined that namespace in the same scope.
Inline Tables Are Immutable
Once an inline table is defined, you cannot add keys to it with dotted notation elsewhere in the file:
# INVALID
server = {host = "localhost"}
server.port = 8080 # Error: cannot extend an inline table
Use a regular table header [server] instead of an inline table if you need to add keys in separate lines.
Array Element Type Consistency
In TOML 0.5 and earlier, all elements in an array had to be the same type. TOML 1.0 relaxed this restriction, but many parsers still enforce homogeneous arrays. Be explicit about types to ensure maximum parser compatibility:
# May fail in older parsers
values = [1, "two", 3.0]
# Safer — explicit string conversion
values = ["1", "two", "3.0"]
Super Tables Cannot Follow Their Sub-Tables
In TOML, a table header must appear before any of its sub-tables. The following is invalid:
# INVALID: [fruit] must come before [fruit.apple]
[fruit.apple]
color = "red"
[fruit] # Error: [fruit] defined after [fruit.apple]
category = "produce"
Dotted Keys and Table Headers Conflict
A dotted key and a table header that resolve to the same path will conflict. Always be consistent within a file — use either dotted keys or table headers to define a namespace, not both:
# INVALID: both define the same 'server' namespace
server.host = "localhost"
[server] # Error: conflicts with dotted key above
port = 8080
9. Frequently Asked Questions
What does TOML stand for?
TOML stands for Tom's Obvious, Minimal Language, created by Tom Preston-Werner (co-founder of GitHub) in 2013. The name signals its philosophy: it should be obviously readable to humans, minimal in complexity, and easy to parse into a hash table data structure. TOML reached version 1.0 in January 2021, which provided a stable specification that the ecosystem has built upon. Files use the .toml extension.
Is TOML better than YAML?
TOML excels at flat-to-moderately-nested configuration with clear key-value semantics. It has no implicit type coercion (no Norway problem), a stable unambiguous specification, and native datetime support. YAML excels at complex nested structures, large configuration hierarchies, and environments where anchors and aliases provide significant DRY benefits. TOML is the better choice for tool-specific configuration files (Cargo.toml, pyproject.toml). YAML is the better choice for orchestration and workflow definitions (Kubernetes, GitHub Actions, Ansible). The two formats fill different niches rather than directly competing.
What files use TOML?
The most widely encountered TOML files are Cargo.toml (Rust package and workspace configuration), pyproject.toml (Python project metadata per PEP 517/518/621), netlify.toml (Netlify deployment and redirect configuration), config.toml (Hugo static site generator), and .cargo/config.toml (Rust build and target configuration). Many newer tools choose TOML for their configuration format because of its stability and clarity — it is increasingly common in Rust-written CLI tools and DevOps utilities.
How do I validate a TOML file?
The most complete CLI tool is taplo: install it with cargo install taplo-cli and run taplo check yourfile.toml. In Python 3.11+, the standard library includes tomllib: run python -c "import tomllib; tomllib.load(open('yourfile.toml','rb'))" — any syntax error will raise a TOMLDecodeError. For CI/CD, integrate taplo or a language-native TOML parser into a validation step that runs on every push and pull request. Catching TOML errors in CI is far cheaper than discovering them after a failed deployment.
Work with YAML and JSON Alongside TOML
Need to convert YAML configuration to JSON for API consumption? The SnapUtils YAML to JSON converter handles the conversion instantly — paste your YAML and get valid JSON output in one click.
Open YAML to JSON Converter FreeRelated guides: YAML vs JSON • HTML Entities Guide • URL Slug Guide