TOML Configuration Guide: Syntax, Tables, and Arrays
Learn TOML syntax for configuration files, including tables, arrays, inline tables, and how it compares to YAML and JSON for project configuration.
TOML (Tom’s Obvious, Minimal Language) is a configuration file format designed to be easy to read and unambiguous to parse. You have probably encountered it already — Rust’s Cargo.toml, Python’s pyproject.toml, Hugo’s config files, and many other tools use TOML as their primary configuration format.
TOML occupies a sweet spot between JSON (too strict, no comments) and YAML (too flexible, easy to make mistakes). This guide covers the syntax you need, common patterns, and how TOML stacks up against the alternatives.
Basic Syntax
Key-Value Pairs
TOML files are built on key-value pairs. Each pair goes on its own line.
name = "my-project"
version = "1.0.0"
debug = true
max_retries = 3
pi = 3.14159
Keys are bare (unquoted) by default. If a key contains spaces or special characters, quote it:
"content-type" = "application/json"
"server.host" = "localhost"
Data Types
TOML has clear, unambiguous data types:
| Type | Example |
|---|---|
| String | name = "hello" |
| Integer | port = 8080 |
| Float | rate = 0.5 |
| Boolean | enabled = true |
| Date | created = 2026-07-21 |
| Time | start = 08:30:00 |
| DateTime | published = 2026-07-21T10:30:00Z |
Strings can be multi-line using triple quotes:
description = """
This is a long description
that spans multiple lines.
Newlines are preserved."""
For literal strings (no escape sequences), use single quotes:
path = 'C:\Users\name\Documents'
regex = '\d+\.\d+'
Comments
TOML supports comments with #. This alone makes it more practical than JSON for configuration files.
# Database configuration
host = "localhost" # Override in production
port = 5432
Tables
Tables are TOML’s equivalent of objects or dictionaries. They group related key-value pairs under a header.
[database]
host = "localhost"
port = 5432
name = "myapp"
[server]
host = "0.0.0.0"
port = 8080
workers = 4
This is equivalent to the following JSON:
{
"database": {
"host": "localhost",
"port": 5432,
"name": "myapp"
},
"server": {
"host": "0.0.0.0",
"port": 8080,
"workers": 4
}
}
Nested Tables
Use dotted keys for nested structures:
[database.connection]
host = "localhost"
port = 5432
[database.pool]
min_size = 5
max_size = 20
Or use dotted keys inline:
database.connection.host = "localhost"
database.connection.port = 5432
Inline Tables
For small tables, you can write them on a single line:
point = { x = 1, y = 2 }
database = { host = "localhost", port = 5432 }
Inline tables must fit on one line and cannot be extended later in the file.
Arrays
Arrays use square brackets:
ports = [8080, 8081, 8082]
tags = ["web", "production", "v2"]
Multi-line arrays are supported:
allowed_origins = [
"https://example.com",
"https://staging.example.com",
"http://localhost:3000",
]
Note that TOML allows trailing commas in arrays, unlike JSON.
Arrays of Tables
This is TOML’s most distinctive feature. Double brackets define arrays of tables:
[[plugins]]
name = "eslint"
enabled = true
[[plugins]]
name = "prettier"
enabled = true
[[plugins]]
name = "typescript"
enabled = false
This produces an array of three objects. In JSON:
{
"plugins": [
{ "name": "eslint", "enabled": true },
{ "name": "prettier", "enabled": true },
{ "name": "typescript", "enabled": false }
]
}
You can nest arrays of tables:
[[servers]]
name = "web-1"
[[servers.interfaces]]
ip = "10.0.0.1"
type = "private"
[[servers.interfaces]]
ip = "203.0.113.1"
type = "public"
[[servers]]
name = "web-2"
[[servers.interfaces]]
ip = "10.0.0.2"
type = "private"
Real-World Examples
Python pyproject.toml
[project]
name = "my-package"
version = "0.1.0"
description = "A useful package"
requires-python = ">=3.9"
dependencies = [
"requests>=2.28",
"click>=8.0",
]
[project.scripts]
my-cli = "my_package.cli:main"
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = "-v --tb=short"
[tool.ruff]
line-length = 100
target-version = "py39"
Rust Cargo.toml
[package]
name = "my-crate"
version = "0.1.0"
edition = "2021"
authors = ["Your Name <you@example.com>"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
[dev-dependencies]
assert_cmd = "2.0"
[[bin]]
name = "my-tool"
path = "src/main.rs"
TOML vs YAML vs JSON
Each format has its place. Here is how they compare for configuration. For a deeper side-by-side comparison, see our guide to choosing between YAML, JSON, and TOML.
| Feature | TOML | YAML | JSON |
|---|---|---|---|
| Comments | Yes | Yes | No |
| Readability | High | High | Moderate |
| Type safety | Strong | Weak | Moderate |
| Nested data | Tables | Indentation | Braces |
| Trailing commas | Yes (arrays) | N/A | No |
| Multi-line strings | Yes | Yes | No |
| Date/time types | Native | No (strings) | No (strings) |
| Spec complexity | Low | Very high | Very low |
Where TOML Wins
- Unambiguous parsing: YAML’s
nois a boolean,Nois a boolean, and"No"is a string. In TOML,falseis a boolean and"No"is a string. No surprises. - Flat structures: TOML handles configuration with a few levels of nesting cleanly.
- Type clarity: Every value has a clear, visible type. No implicit type coercion.
Where TOML Struggles
- Deep nesting: If your config has many levels of nesting, the table headers get long and repetitive. YAML handles deep hierarchies more gracefully.
- Complex data structures: Mixed arrays of different types, deeply nested objects, and highly dynamic schemas are awkward in TOML.
- Familiarity: JSON and YAML are more widely known. Team members may need to learn TOML syntax.
Converting Between Formats
When working across projects that use different configuration formats, you often need to convert between them. Our TOML to JSON converter handles the translation cleanly, preserving types and structure. If you need to go between TOML and YAML, the TOML to YAML converter handles that conversion as well. Before committing a config file, you can also check its syntax with our TOML Formatter, which reformats and pretty-prints TOML in the browser.
These conversions are particularly useful when:
- Migrating a project from one config format to another
- Comparing configurations side by side
- Processing TOML data with JSON-based tooling
- Generating TOML from a JSON API response
Best Practices
-
Group related settings under tables. Do not leave everything at the top level.
-
Use comments liberally. Explain why a value is set, not just what it is.
max_retries = 3 # Keep low to fail fast during health checksis better thanmax_retries = 3 # max retries. -
Prefer bare keys when possible. Only quote keys that contain special characters.
-
Use arrays of tables for lists of structured items. They are more readable than inline alternatives for anything beyond trivial data.
-
Keep files focused. A single TOML file should configure one thing. If your config is growing past a few hundred lines, consider splitting it.
-
Validate your TOML. Syntax errors can be subtle. Use a TOML Validator or parser to catch issues before they cause runtime failures.
Summary
TOML fills the gap between JSON’s rigidity and YAML’s complexity. It gives you comments, clear types, and readable structure without the parsing ambiguities that plague YAML. If you are starting a new project and need a configuration format, TOML is an excellent default choice. It is especially well-suited for project metadata, tool configuration, and any settings file that humans need to read and edit regularly.
Comments