# 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.

- URL: https://www.browserutils.dev/blog/toml-configuration-guide
- Published: 2026-09-21
- Updated: 2026-07-02

---

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.

```toml
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:

```toml
"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:

```toml
description = """
This is a long description
that spans multiple lines.
Newlines are preserved."""
```

For literal strings (no escape sequences), use single quotes:

```toml
path = 'C:\Users\name\Documents'
regex = '\d+\.\d+'
```

### Comments

TOML supports comments with `#`. This alone makes it more practical than JSON for configuration files.

```toml
# 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.

```toml
[database]
host = "localhost"
port = 5432
name = "myapp"

[server]
host = "0.0.0.0"
port = 8080
workers = 4
```

This is equivalent to the following JSON:

```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:

```toml
[database.connection]
host = "localhost"
port = 5432

[database.pool]
min_size = 5
max_size = 20
```

Or use dotted keys inline:

```toml
database.connection.host = "localhost"
database.connection.port = 5432
```

### Inline Tables

For small tables, you can write them on a single line:

```toml
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:

```toml
ports = [8080, 8081, 8082]
tags = ["web", "production", "v2"]
```

Multi-line arrays are supported:

```toml
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:

```toml
[[plugins]]
name = "eslint"
enabled = true

[[plugins]]
name = "prettier"
enabled = true

[[plugins]]
name = "typescript"
enabled = false
```

This produces an array of three objects. In JSON:

```json
{
  "plugins": [
    { "name": "eslint", "enabled": true },
    { "name": "prettier", "enabled": true },
    { "name": "typescript", "enabled": false }
  ]
}
```

You can nest arrays of tables:

```toml
[[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

```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

```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](/blog/yaml-vs-json-vs-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 `no` is a boolean, `No` is a boolean, and `"No"` is a string. In TOML, `false` is 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](/tools/toml-to-json) handles the translation cleanly, preserving types and structure. If you need to go between TOML and YAML, the [TOML to YAML converter](/tools/toml-to-yaml) handles that conversion as well. Before committing a config file, you can also check its syntax with our [TOML Formatter](/tools/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

1. **Group related settings** under tables. Do not leave everything at the top level.

2. **Use comments liberally**. Explain why a value is set, not just what it is. `max_retries = 3  # Keep low to fail fast during health checks` is better than `max_retries = 3  # max retries`.

3. **Prefer bare keys** when possible. Only quote keys that contain special characters.

4. **Use arrays of tables** for lists of structured items. They are more readable than inline alternatives for anything beyond trivial data.

5. **Keep files focused**. A single TOML file should configure one thing. If your config is growing past a few hundred lines, consider splitting it.

6. **Validate your TOML**. Syntax errors can be subtle. Use a [TOML Validator](/tools/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.