Si has abierto un proyecto Rust, has conocido TOML. Si has tocado un paquete Python que usa
pyproject.toml, también lo has usado. TOML — Tom's Obvious Minimal Language —
es el formato de archivo de configuración que sigue ganando silenciosamente a los desarrolladores que están cansados de
las trampas de espacios en blanco de YAML y la negativa de JSON a permitir comentarios. Creado por Tom Preston-Werner,
cofundador de GitHub, TOML fue diseñado en torno a una idea: un formato de configuración debe ser tan obvio que puedas
leerlo sin una especificación. Veamos si cumple con esa promesa.
¿Qué es exactamente TOML?
TOML es un formato de archivo de configuración con tres objetivos de diseño explícitos, enunciados al principio de la especificación oficial: debe ser obvio de leer, minimal en complejidad, y mapearse sin ambigüedad a una tabla hash (un diccionario/map en la mayoría de los lenguajes). Ese tercer objetivo es el clave — cada archivo TOML válido tiene exactamente un resultado de análisis correcto. Sin coerción de tipos sorprendente, sin trampas booleanas al estilo YAML, sin ambigüedad sobre si un valor es una cadena o un número.
El formato toma prestado el estilo de encabezado de sección de los viejos archivos INI pero añade tipos apropiados, arrays y tablas anidadas encima. El resultado es algo que parece familiar a cualquiera que haya editado un archivo de configuración antes, pero con suficiente estructura para que un analizador pueda darte un modelo de datos tipado real. La versión 1.0.0 de la especificación se lanzó en enero de 2021 después de años de refinamiento — puedes explorar la especificación TOML en GitHub si quieres profundizar en los casos extremos.
Lo básico: pares clave-valor y comentarios
Un archivo TOML está compuesto de pares clave-valor. Las claves y los valores están separados por =,
y los comentarios comienzan con #. Simple.
# Cargo.toml — Rust package manifest
[package]
name = "image-resizer"
version = "0.4.2"
edition = "2021"
authors = ["Ada Lovelace <[email protected]>"]
description = "Fast image resizing with Lanczos3 resampling"
license = "MIT"
repository = "https://github.com/example/image-resizer"
# Integers, floats, booleans — all native types
max_threads = 8
quality_default = 0.85
verbose_logging = falseNo se requieren comillas en la mayoría de las claves. Los valores están tipados — 8 es un entero, 0.85
es un flotante, false es un booleano. Sin adivinar, sin coerción implícita basada en cómo se ve el valor.
Este es el TOML cotidiano que escribirás el 90% del tiempo.
Tipos de cadenas: básico, literal y multilínea
TOML tiene cuatro tipos de cadenas. Esto cubre cada caso real de forma limpia:
# Basic strings — double quotes, support escape sequences
greeting = "Hello, \nworld!"
path = "C:\\Users\\ada\\Documents"
# Literal strings — single quotes, no escape processing at all
regex_pattern = '\d{4}-\d{2}-\d{2}'
windows_path = 'C:\Users\ada'
# Multiline basic string — triple double quotes
sql_query = """
SELECT user_id, email, created_at
FROM users
WHERE active = true
AND created_at > '2024-01-01'
ORDER BY created_at DESC
"""
# Multiline literal string — triple single quotes, no escapes
shell_script = '''
#!/bin/bash
echo "Deploying $APP_NAME to $ENV"
kubectl apply -f k8s/
'''El tipo de cadena literal ('comillas simples') es el que la gente olvida que existe, y es
genuinamente útil — los patrones regex y las rutas de Windows son mucho más limpias sin doblar barras invertidas.
Elige cualquier estilo de comilla que signifique escribir menos barras invertidas.
Números, booleanos y fechas/horas
Los tipos nativos de TOML cubren todo lo que pondrías realmente en un archivo de configuración. Notablemente, tiene soporte datetime de primera clase — algo que YAML técnicamente tiene pero maneja inconsistentemente entre analizadores.
# Integers — underscores allowed as separators (like numeric literals in code)
max_connections = 1_000_000
port = 5432
hex_color = 0xFF6B6B # hex prefix supported
octal_permissions = 0o755 # octal prefix supported
# Floats
pi = 3.14159265
compression_ratio = 1.5e-3
infinity_val = inf # special values: inf, -inf, nan
# Booleans — lowercase only (not True, TRUE, yes, on)
ssl_enabled = true
dry_run = false
# Datetimes — RFC 3339 format
created_at = 2024-03-15T09:30:00Z
updated_at = 2024-03-15T14:22:10+05:30
log_date = 2024-03-15 # local date (no time)
backup_time = 03:00:00 # local time (no date)true y false —
no True, TRUE, yes, on, ni ninguna de las otras variantes
que YAML 1.1 acepta. Esto es intencional. Si necesitas una cadena "true", ponla entre comillas.Tablas: los encabezados de sección estilo INI
Las tablas en TOML se definen con la sintaxis [encabezado]. Todo lo que está debajo de un encabezado pertenece
a esa tabla hasta que aparezca el siguiente encabezado. Esta es la característica que hace que TOML parezca familiar — es
esencialmente archivos INI pero con tipos.
[database]
host = "db.internal"
port = 5432
name = "app_production"
pool_size = 20
[database.credentials]
username = "app_user"
# Don't put real passwords here — use env vars or a secrets manager
password_env = "DB_PASSWORD"
[server]
host = "0.0.0.0"
port = 8080
workers = 4
[server.tls]
enabled = true
cert_file = "/etc/ssl/certs/app.crt"
key_file = "/etc/ssl/private/app.key"Los encabezados punteados como [database.credentials] crean tablas anidadas. El resultado analizado
es exactamente lo que esperarías: un objeto database con un objeto credentials anidado.
También puedes escribir tablas en línea para casos simples — más sobre eso a continuación.
Arrays y array de tablas
Los arrays en TOML usan corchetes y pueden abarcar múltiples líneas. La característica realmente distintiva de TOML
es el Array de Tablas — definido con corchetes dobles [[encabezado]]. Esta es la respuesta de TOML
a "¿cómo expreso una lista de objetos?" sin que parezca JSON.
# Regular arrays — can be split across lines, trailing comma is fine
allowed_origins = [
"https://app.example.com",
"https://admin.example.com",
"http://localhost:3000",
]
supported_formats = ["jpeg", "png", "webp", "avif"]
retry_delays_ms = [100, 250, 500, 1000, 2000]
# Array of Tables — [[double brackets]]
# Each [[servers]] header appends a new object to the servers array
[[servers]]
name = "web-01"
ip = "10.0.1.10"
role = "primary"
tags = ["web", "prod"]
[[servers]]
name = "web-02"
ip = "10.0.1.11"
role = "replica"
tags = ["web", "prod"]
[[servers]]
name = "db-01"
ip = "10.0.2.10"
role = "primary"
tags = ["database", "prod"]Esa sintaxis [[servers]] se analiza en un array de tres objetos — equivalente a
"servers": [{...}, {...}, {...}] en JSON. Es más verboso comparado con los arrays de objetos JSON,
pero la ventaja es la legibilidad cuando cada elemento tiene muchos campos. Puedes ver este patrón ampliamente en los
manifiestos Cargo.toml
para definir múltiples objetivos binarios, ejemplos y entradas bench.
Tablas en línea: compactas en una sola línea
Cuando una tabla solo tiene un par de campos y no quieres un encabezado de sección completo para ella, las tablas en línea te permiten escribirla en una sola línea:
[build]
# Inline table — must stay on one line
target = { arch = "x86_64", os = "linux", libc = "musl" }
# Equivalent to writing:
# [build.target]
# arch = "x86_64"
# os = "linux"
# libc = "musl"
[feature_flags]
auth = { enabled = true, rollout_pct = 100 }
search = { enabled = true, rollout_pct = 50 }
beta = { enabled = false, rollout_pct = 0 }Las tablas en línea deben permanecer en una sola línea y no pueden extenderse más tarde con un [encabezado].
Úsalas para pequeños grupos de valores cohesivos — son excelentes para cosas como pares de coordenadas, objetivos de compilación
o configuraciones de indicadores simples. No las uses cuando tienes más de tres o cuatro campos; en ese punto,
una tabla regular es más legible.
TOML en el mundo real
TOML ha creado un nicho sólido en los ecosistemas Rust y Python, y está ganando tracción en otros lugares. Aquí es donde lo encontrarás día a día:
- Rust — Cargo.toml: Cada proyecto Rust tiene uno. Define metadatos del paquete, dependencias, características y objetivos de compilación. La referencia del manifiesto Cargo es la guía de uso de TOML más detallada del mundo real que encontrarás.
- Python — pyproject.toml: PEP 518 y PEP 621 estandarizaron TOML como el formato de metadatos de proyecto Python. Poetry, Hatch, PDM y setuptools todos leen desde él.
- Deno: El archivo de configuración de Deno soporta TOML además de JSON.
- Hugo: El generador de sitios estáticos Hugo acepta TOML como formato de configuración y front-matter — lo verás entre delimitadores
+++al principio de archivos Markdown. - uv: El rápido gestor de paquetes Python de Astral usa pyproject.toml para toda la configuración, haciendo de TOML el estándar de facto para las nuevas herramientas Python.
Si necesitas convertir una configuración existente entre formatos, los conversores TOML a JSON y JSON a TOML manejan el mapeo estructural por ti. O usa el Formateador TOML para limpiar el espaciado inconsistente en un archivo existente, y el Validador TOML para detectar errores de sintaxis antes de que aparezcan en producción.
Análisis de TOML en Python
Desde Python 3.11, la biblioteca estándar incluye
tomllib
— sin dependencia externa necesaria. Para Python 3.9 y 3.10, el backport
tomli
tiene una API idéntica, por lo que puedes cambiar entre ellos con un solo alias de importación.
import sys
if sys.version_info >= (3, 11):
import tomllib
else:
import tomli as tomllib # pip install tomli
# tomllib only reads binary mode — open with "rb"
with open("pyproject.toml", "rb") as f:
config = tomllib.load(f)
# Types match TOML exactly: str, int, float, bool, datetime, list, dict
project_name = config["project"]["name"] # str
python_requires = config["project"]["requires-python"] # str
dependencies = config["project"]["dependencies"] # list[str]
print(f"Project: {project_name}")
print(f"Requires Python: {python_requires}")
print(f"Dependencies ({len(dependencies)}):")
for dep in dependencies:
print(f" {dep}")
# Parse from a string with tomllib.loads()
raw = """
[server]
host = "localhost"
port = 8080
debug = true
"""
server_config = tomllib.loads(raw)
print(server_config["server"]["port"]) # 8080 (int, not "8080")Ten en cuenta que tomllib.load() requiere modo binario ("rb"). Esto es intencional —
TOML requiere codificación UTF-8, y abrir en modo binario permite al analizador manejar la verificación de codificación él mismo.
Es una pequeña trampa que atrapa a la gente la primera vez.
Análisis de TOML en Rust
En Rust, la crate toml
es la elección estándar. Se integra estrechamente con serde, por lo que puedes deserializar directamente en
tus propias estructuras con un mínimo de código repetitivo:
use serde::Deserialize;
use std::fs;
#[derive(Debug, Deserialize)]
struct AppConfig {
server: ServerConfig,
database: DatabaseConfig,
feature_flags: FeatureFlags,
}
#[derive(Debug, Deserialize)]
struct ServerConfig {
host: String,
port: u16,
workers: usize,
}
#[derive(Debug, Deserialize)]
struct DatabaseConfig {
host: String,
port: u16,
name: String,
pool_size: u32,
}
#[derive(Debug, Deserialize)]
struct FeatureFlags {
enable_beta: bool,
max_upload_mb: u32,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let raw = fs::read_to_string("config.toml")?;
let config: AppConfig = toml::from_str(&raw)?;
println!("Server: {}:{}", config.server.host, config.server.port);
println!("DB pool size: {}", config.database.pool_size);
println!("Beta enabled: {}", config.feature_flags.enable_beta);
Ok(())
}Añade toml = "0.8" y serde = { version = "1", features = ["derive"] }
a tus [dependencies] en Cargo.toml y listo. Las macros derive de serde manejan todo el
mapeo de campos. Si los nombres de campo de tu estructura usan snake_case pero las claves TOML usan kebab-case, añade
#[serde(rename_all = "kebab-case")] a nivel de estructura y todo se mapea automáticamente.
TOML vs YAML vs JSON — Cuándo elegir cuál
Esta pregunta surge en cada nuevo proyecto. Aquí está el desglose honesto:
- TOML: Mejor para archivos de configuración que los humanos escriben y mantienen, donde los valores tipados importan y el anidamiento es superficial a moderado. Punto fuerte: configuraciones de aplicaciones, manifiestos de compilación, configuraciones de herramientas. Falla para el anidamiento profundo — 4+ niveles se vuelven incómodos con encabezados de sección repetidos.
- YAML: Mejor cuando estás escribiendo datos estructurados con muchas listas de objetos (manifiestos Kubernetes, flujos de trabajo de GitHub Actions). El soporte de cadenas multilínea es genuinamente mejor que el de TOML. La desventaja: la sensibilidad a los espacios en blanco y la coerción de tipos de YAML 1.1 crean errores reales en la práctica.
- JSON: Mejor para el intercambio de datos entre máquinas, APIs y cuando necesitas el soporte de herramientas más amplio posible. No es ideal para la configuración mantenida por humanos — sin comentarios, y el escape de cadenas es tedioso.
Un pyproject.toml real
Para poner todo junto, aquí hay un pyproject.toml realista para una biblioteca Python —
el tipo que encontrarías en un proyecto de código abierto moderno. Observa cómo el formato lleva mucha información
estructurada mientras sigue siendo fácil de escanear:
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "httpx-cache"
version = "1.2.0"
description = "Transparent HTTP caching layer for httpx"
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.9"
authors = [
{ name = "Ada Lovelace", email = "[email protected]" },
]
keywords = ["http", "cache", "httpx", "async"]
classifiers = [
"Development Status :: 4 - Beta",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
dependencies = [
"httpx>=0.25.0",
"anyio>=4.0.0",
]
[project.optional-dependencies]
redis = ["redis>=5.0.0"]
dev = [
"pytest>=7.4.0",
"pytest-asyncio>=0.23.0",
"coverage[toml]>=7.3.0",
"ruff>=0.1.0",
"mypy>=1.7.0",
]
[project.urls]
Homepage = "https://github.com/example/httpx-cache"
Changelog = "https://github.com/example/httpx-cache/blob/main/CHANGELOG.md"
[tool.ruff]
line-length = 100
target-version = "py39"
[tool.ruff.lint]
select = ["E", "F", "I", "UP"]
[tool.mypy]
strict = true
python_version = "3.9"
[tool.coverage.run]
source = ["httpx_cache"]
branch = true
[tool.coverage.report]
fail_under = 90Este es TOML real haciendo trabajo real. Cada sección [tool.x] es un espacio de nombres separado
para una herramienta diferente — ruff, mypy, coverage — todos viviendo en un archivo sin pisarse mutuamente.
No se requiere anidamiento profundo, todo es legible de un vistazo.
Conclusión
TOML cumple su promesa: legible, sin ambigüedades y limpiamente tipado. Si estás empezando un nuevo proyecto en Rust, Python o Go, TOML vale la pena como predeterminado para los archivos de configuración — especialmente los archivos que se comprometerán al control de fuente y serán editados por varias personas. La falta de sensibilidad a los espacios en blanco por sí sola lo hace un alivio comparado con YAML. Para trabajar con archivos TOML directamente en tu navegador, las herramientas Formateador TOML y Validador TOML manejan las tareas más comunes. Y si estás migrando un proyecto existente desde JSON o necesitas hacer puente TOML a un pipeline basado en JSON, los conversores TOML a JSON y JSON a TOML te tienen cubierto.