TOML Tools

Parse and convert TOML configuration files

Essential for Rust projects, modern configuration management, and settings files. TOML (Tom's Obvious, Minimal Language) combines the simplicity of INI files with the power of modern data types. Perfect for Cargo.toml, pyproject.toml, and application configurations that need to be human-readable and easy to edit.

What is TOML?

TOML (Tom's Obvious, Minimal Language) is a configuration file format created by Tom Preston-Werner (co-founder of GitHub) in 2013. It's designed to be easy to read due to obvious semantics, while still mapping unambiguously to a hash table.

History & Creation

TOML was created by Tom Preston-Werner on February 23, 2013, as a response to the complexity of YAML and the limitations of JSON for configuration files. Version 1.0.0 was released on January 12, 2021, after 8 years of refinement. The goal was to create a format that's minimal, obvious, and easy to parse.

Where TOML is Used

  • Rust's Cargo.toml package management
  • Python's pyproject.toml (PEP 518)
  • Hugo static site generator config
  • Netlify deployment configuration
  • GitLab CI/CD configuration
  • Application settings files
  • Build tool configurations

Benefits Over Other Formats

  • vs JSON: Comments support, more readable, trailing commas allowed, multi-line strings
  • vs YAML: No ambiguity, simpler spec, no significant whitespace issues, easier to parse
  • vs INI: Nested tables, arrays, proper data types, standardized format
  • vs XML: Human-readable, minimal syntax, no closing tags, native data types

TOML Design Principles

Obvious

A person should be able to read a TOML file and understand what it means without learning the format.

Minimal

The format should be simple with a small specification that's easy to implement correctly.

Unambiguous

A TOML file should map to a single hash table without any ambiguity or edge cases.

Working with TOML Across Languages

🦀 Rust

  • Built-in Cargo support
  • toml crate for parsing
  • serde integration
  • cargo edit for CLI editing

🐍 Python

  • tomllib (Python 3.11+)
  • toml package
  • poetry uses pyproject.toml
  • pip supports PEP 518

📦 Node.js

  • @iarna/toml package
  • toml npm package
  • smol-toml for small size
  • Used in various tools

Convert TOML to JSON

Parse TOML files and convert them to JSON format

TOML Structure Examples & Best Practices

TOML's clarity and simplicity make it ideal for configuration files. Here are comprehensive examples demonstrating its features and best practices.

Basic TOML Structures

Simple Key-Value Pairs

Basic configuration with various data types

# This is a TOML document
title = "TOML Example"

[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00 # Datetime with offset

[database]
server = "192.168.1.1"
ports = [ 8001, 8001, 8002 ]
connection_max = 5000
enabled = true

Tables and Nested Tables

Organizing configuration with table structures

[server]
host = "localhost"
port = 8080

[server.ssl]
enabled = true
cert = "/path/to/cert.pem"
key = "/path/to/key.pem"

# Inline table for compact representation
[server.limits]
request = { max_size = "10MB", timeout = 30 }

[clients.alpha]
ip = "10.0.0.1"
dc = "eqdc10"

[clients.beta]
ip = "10.0.0.2"
dc = "eqdc10"

Real-World Configuration Examples

Rust Cargo.toml

Package manifest for Rust projects

[package]
name = "my-app"
version = "0.1.0"
authors = ["Jane Doe <jane@example.com>"]
edition = "2021"
license = "MIT OR Apache-2.0"
description = "A blazingly fast web server"
repository = "https://github.com/example/my-app"
keywords = ["web", "server", "http"]
categories = ["web-programming::http-server"]

[dependencies]
tokio = { version = "1.35", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
axum = "0.7"
tracing = "0.1"
tracing-subscriber = "0.3"

[dev-dependencies]
cargo-watch = "8.4"
pretty_assertions = "1.4"

[profile.release]
opt-level = 3
lto = true
codegen-units = 1

[[bin]]
name = "server"
path = "src/main.rs"

Python pyproject.toml

Modern Python project configuration (PEP 518/621)

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "awesome-package"
version = "2.0.0"
authors = [
  { name="Jane Smith", email="jane@example.com" },
]
description = "A small but awesome package"
readme = "README.md"
license = {file = "LICENSE"}
requires-python = ">=3.8"
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
    "Operating System :: OS Independent",
]
dependencies = [
    "httpx>=0.23",
    "pydantic>=2.0",
    "rich>=13.0",
]

[project.optional-dependencies]
dev = [
    "pytest>=7.0",
    "black>=23.0",
    "mypy>=1.0",
    "ruff>=0.1",
]

[project.urls]
"Homepage" = "https://github.com/example/awesome-package"
"Bug Tracker" = "https://github.com/example/awesome-package/issues"

[tool.black]
line-length = 88
target-version = ['py38', 'py39', 'py310', 'py311']

[tool.ruff]
line-length = 88
select = ["E", "F", "I", "N", "W"]

[tool.mypy]
python_version = "3.8"
warn_return_any = true
warn_unused_configs = true

Application Configuration

Complex application settings with multiple environments

# Application Configuration
title = "My Awesome App"
version = "1.0.0"

[app]
name = "awesome-app"
debug = false
log_level = "info"

# Arrays of tables for multiple instances
[[app.workers]]
id = "worker-1"
type = "cpu"
threads = 4

[[app.workers]]
id = "worker-2"
type = "io"
threads = 2

# Environment-specific settings
[environments.development]
database_url = "postgresql://localhost/myapp_dev"
redis_url = "redis://localhost:6379/0"
api_keys = [
    { name = "stripe", key = "sk_test_..." },
    { name = "sendgrid", key = "SG.test..." }
]

[environments.production]
database_url = "postgresql://prod-db.example.com/myapp"
redis_url = "redis://prod-redis.example.com:6379/0"
api_keys = [
    { name = "stripe", key = "sk_live_..." },
    { name = "sendgrid", key = "SG.prod..." }
]

[features]
enable_analytics = true
enable_notifications = true
experimental_features = ["dark_mode", "ai_assistant"]

[cache]
driver = "redis"
ttl = 3600
prefix = "myapp:"

[cache.options]
max_connections = 100
idle_timeout = 300

Advanced TOML Features

Multi-line Strings

Different ways to handle long text in TOML

# Basic strings
str1 = "I'm a string."
str2 = 'You can "quote" me.'
str3 = 'Name	José
Loc	SF.'

# Multi-line basic strings
str4 = """
Roses are red
Violets are blue"""

# Line ending backslash
str5 = """  The quick brown   fox jumps over   the lazy dog.  """

# Literal strings (no escaping)
winpath = 'C:Users
odejs	emplates'
winpath2 = '\ServerXadmin$system32'
quoted = 'Tom "Dubs" Preston-Werner'

# Multi-line literal strings
regex = '''I [dw]on't need d{2} apples'''
lines = '''
The first newline is
trimmed in raw strings.
   All other whitespace
   is preserved.
'''

Arrays and Mixed Types

Working with arrays of different types

# Arrays of integers
integers = [ 1, 2, 3 ]

# Arrays of different types (each array must be homogeneous)
colors = [ "red", "yellow", "green" ]
numbers = [ 0.1, 0.2, 0.5, 1, 2, 5 ]

# Nested arrays
nested_arrays = [ [ 1, 2 ], [3, 4, 5] ]
nested_mixed = [ [ 1, 2 ], ["a", "b", "c"] ]

# Arrays can span multiple lines
integers2 = [
  1, 2, 3
]

integers3 = [
  1,
  2, # this is ok
]

Dates and Times

TOML's native datetime support

# Offset Date-Time
odt1 = 1979-05-27T07:32:00Z
odt2 = 1979-05-27T00:32:00-07:00
odt3 = 1979-05-27T00:32:00.999999-07:00

# Local Date-Time (no timezone)
ldt1 = 1979-05-27T07:32:00
ldt2 = 1979-05-27T00:32:00.999999

# Local Date
ld1 = 1979-05-27

# Local Time
lt1 = 07:32:00
lt2 = 00:32:00.999999

# Using in configuration
[deployment]
scheduled_at = 2024-01-20T15:00:00Z
maintenance_window = { start = 02:00:00, end = 04:00:00 }
backup_retention_days = 30

Static Site Generator Config

Hugo Configuration

Static site generator configuration example

baseURL = "https://example.com/"
languageCode = "en-us"
title = "My Personal Blog"
theme = "hugo-theme-stack"
paginate = 10

[params]
author = "John Doe"
description = "Personal blog about technology and life"
mainSections = ["posts", "docs"]
featuredImageField = "image"
rssFullContent = true
favicon = "/favicon.ico"

[params.footer]
since = 2023
customText = "CC BY-NC-SA 4.0"

[params.comments]
enabled = true
provider = "utterances"

[params.comments.utterances]
repo = "username/blog-comments"
issueTerm = "pathname"
label = "comment"
theme = "github-light"

[[menu.main]]
identifier = "home"
name = "Home"
url = "/"
weight = 1

[[menu.main]]
identifier = "posts"
name = "Posts"
url = "/posts/"
weight = 2

[[menu.main]]
identifier = "about"
name = "About"
url = "/about/"
weight = 3

[markup]
  [markup.highlight]
    anchorLineNos = false
    codeFences = true
    guessSyntax = false
    hl_Lines = ""
    lineAnchors = ""
    lineNoStart = 1
    lineNos = false
    lineNumbersInTable = true
    noClasses = true
    style = "monokai"
    tabWidth = 4

Best Practices and Common Patterns

File Organization

  • Start with a clear title or description comment
  • Group related settings in tables
  • Use consistent naming (snake_case is common)
  • Place metadata tables (like [package]) at the top
  • Keep arrays of tables at the bottom

Common Pitfalls to Avoid

  • Redefining tables: Can't define [a.b] after [a.b.c]
  • Mixed types in arrays: Arrays must be homogeneous
  • Floating keys: All key/value pairs must belong to a table
  • Invalid key names: Bare keys can only contain A-Z, a-z, 0-9, _, -
  • Ambiguous syntax: Use quotes for keys with special characters

TOML vs Other Formats

Configuration Format Comparison

Same configuration in TOML vs JSON vs YAML

# TOML - Clear and minimal
[app]
name = "myapp"
version = "1.0.0"

[database]
host = "localhost"
port = 5432
credentials = { user = "admin", pass = "secret" }

# JSON equivalent - No comments, more brackets
{
  "app": {
    "name": "myapp",
    "version": "1.0.0"
  },
  "database": {
    "host": "localhost",
    "port": 5432,
    "credentials": {
      "user": "admin",
      "pass": "secret"
    }
  }
}

# YAML equivalent - Whitespace sensitive
app:
  name: myapp
  version: 1.0.0

database:
  host: localhost
  port: 5432
  credentials:
    user: admin
    pass: secret

Parsing TOML in Different Languages

Rust - Using the toml crate

Parsing TOML with serde in Rust

use serde::{Deserialize, Serialize};
use std::fs;

#[derive(Debug, Deserialize, Serialize)]
struct Config {
    app: AppConfig,
    database: DatabaseConfig,
}

#[derive(Debug, Deserialize, Serialize)]
struct AppConfig {
    name: String,
    version: String,
    debug: Option<bool>,
}

#[derive(Debug, Deserialize, Serialize)]
struct DatabaseConfig {
    host: String,
    port: u16,
    credentials: Credentials,
}

#[derive(Debug, Deserialize, Serialize)]
struct Credentials {
    user: String,
    pass: String,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Read and parse TOML file
    let content = fs::read_to_string("config.toml")?;
    let config: Config = toml::from_str(&content)?;
    
    println!("App: {} v{}", config.app.name, config.app.version);
    println!("Database: {}:{}", config.database.host, config.database.port);
    
    // Convert back to TOML
    let toml_string = toml::to_string_pretty(&config)?;
    println!("
{}", toml_string);
    
    Ok(())
}

Python - Using tomllib (3.11+)

Reading TOML files in modern Python

import tomllib  # Python 3.11+
import json
from datetime import datetime
from pathlib import Path

# Read TOML file
with open("config.toml", "rb") as f:
    config = tomllib.load(f)

# Access nested values
app_name = config["app"]["name"]
db_host = config["database"]["host"]
workers = config["app"]["workers"]

print(f"App: {app_name}")
print(f"Database: {db_host}")
print(f"Workers: {len(workers)}")

# For Python < 3.11, use the toml package
# pip install toml
import toml

# Read TOML
with open("config.toml", "r") as f:
    config = toml.load(f)

# Write TOML
data = {
    "project": {
        "name": "example",
        "version": "0.1.0",
        "dependencies": ["requests", "pandas"]
    }
}

with open("output.toml", "w") as f:
    toml.dump(data, f)

# Pretty print with custom encoder
class DateTimeEncoder(toml.TomlEncoder):
    def dump_value(self, v):
        if isinstance(v, datetime):
            return v.isoformat()
        return super().dump_value(v)

JavaScript/Node.js - Using @iarna/toml

Working with TOML in Node.js applications

const fs = require('fs');
const TOML = require('@iarna/toml');

// Read and parse TOML file
const content = fs.readFileSync('config.toml', 'utf-8');
const config = TOML.parse(content);

console.log('App:', config.app.name);
console.log('Database:', config.database.host);

// Create TOML from JavaScript object
const data = {
  package: {
    name: 'my-app',
    version: '1.0.0',
    authors: ['John Doe <john@example.com>']
  },
  dependencies: {
    express: '^4.18.0',
    'body-parser': '^1.20.0'
  },
  scripts: {
    start: 'node index.js',
    test: 'jest'
  }
};

// Convert to TOML string
const tomlString = TOML.stringify(data);
console.log(tomlString);

// Write to file
fs.writeFileSync('output.toml', tomlString);

// Handle dates
const configWithDates = {
  deployment: {
    scheduled: new Date('2024-01-20T15:00:00Z'),
    completed: null
  }
};

const tomlWithDates = TOML.stringify(configWithDates);
console.log(tomlWithDates);

TOML Validation and Schema

Validation Best Practices

Structure Validation
  • Verify required tables exist
  • Check data types match expectations
  • Validate array homogeneity
  • Ensure no duplicate keys
Content Validation
  • Check value ranges (ports, percentages)
  • Validate paths and URLs
  • Verify enum values
  • Confirm required fields present