JSON, YAML, TOML, HCL, and When to Use Each Configuration Format
July 7, 2026 · 10 min read
JSON, YAML, TOML, HCL, and When to Use Each Configuration Format
Every project you ship will have configuration files. Whether it's a package.json locking your dependencies, a docker-compose.yml defining your services, a pyproject.toml managing your Python package, or a main.tf provisioning cloud infrastructure, the format you choose carries real consequences for readability, tooling support, and the likelihood of a misplaced space taking down production. The four formats that dominate modern software — JSON, YAML, TOML, and HCL — each emerged from a different set of constraints and solve different problems. This guide cuts through the noise with a direct technical comparison so you can make the right call the first time.
JSON: The Universal Interchange Format
JSON (JavaScript Object Notation) was specified by Douglas Crockford in 2001 and formally standardized as RFC 7159 in 2014, then superseded by RFC 8259 in 2017. It is built on exactly two structures: objects (unordered key-value pairs) and arrays, making it trivially machine-parseable. Nearly every language ships a JSON parser in its standard library, and the format's strict syntax means no ambiguity — a JSON file either parses or it does not.
{
"name": "my-api",
"version": "2.3.1",
"port": 8080,
"features": {
"cache": true,
"rateLimit": {
"enabled": true,
"requestsPerMinute": 100
}
},
"allowedOrigins": ["https://app.example.com", "https://admin.example.com"]
}
Where JSON excels: API responses, inter-service communication, package.json, and anywhere a machine produces or consumes the file. A JSON Schema can validate it with zero ambiguity, and tools like jq slice and transform it from the command line in a single pipeline.
Where it fails: humans writing JSON by hand. JSON forbids comments, trailing commas, and multi-line strings. A missing comma on line 47 of a 200-line file produces an unhelpful parse error with a byte offset rather than a line number. Those constraints are by design — JSON is an interchange format, not a configuration language — but developers forced to author it manually routinely find it tedious and error-prone.
If you need to move an existing JSON config into something more human-writable, our JSON to YAML Converter migrates the structure in seconds without manual reformatting.
Best fit: API payloads, package.json, machine-generated config, schema-validated data stores.
YAML: Human-Readable but Deceptively Complex
YAML (YAML Ain't Markup Language) v1.2 was released in 2009 and has become the de-facto format for DevOps tooling: Docker Compose, Kubernetes manifests, GitHub Actions, Ansible playbooks, and CircleCI pipelines all rely on it. The appeal is obvious — no braces, no quotes on most strings, and comments with #.
name: my-api
version: "2.3.1"
port: 8080
features:
cache: true
rate_limit:
enabled: true
requests_per_minute: 100
allowed_origins:
- https://app.example.com
- https://admin.example.com
That reads cleanly. The problem is YAML's full specification is 86 pages long, and the format is riddled with edge cases. The Norway problem is the most widely cited: the unquoted string NO is parsed as boolean false in YAML 1.1, which means a country code field silently becomes false. YAML 1.2 fixes many of these coercions, but library support is inconsistent — PyYAML defaulted to 1.1 behavior for years, and gopkg.in/yaml.v2 did the same.
Indentation errors are the other silent killer. A two-space indent where four spaces are expected does not raise a parse error — it restructures your data. In a 500-line Kubernetes manifest, that class of bug can cost hours to track down. YAML does offer genuine power: anchors (&) and aliases (*) for DRY configuration, multi-line strings via | and > block scalars, and explicit type tags. But these features add cognitive load for contributors unfamiliar with the full spec.
Best fit: Kubernetes manifests, GitHub Actions workflows, Docker Compose, Ansible playbooks, and any toolchain that already mandates it.
TOML: The Config File Format That Actually Gets It Right
TOML (Tom's Obvious, Minimal Language) was created by Tom Preston-Werner, GitHub's co-founder, in 2013, with v1.0 finalized in January 2021. The design goal is explicit: be a config file format that maps unambiguously to a hash table. Every valid TOML file has exactly one parse, and the type system is rich without being surprising.
name = "my-api"
version = "2.3.1"
port = 8080
[features]
cache = true
[features.rate_limit]
enabled = true
requests_per_minute = 100
[server]
allowed_origins = [
"https://app.example.com",
"https://admin.example.com",
]
TOML natively supports integers, floats, booleans, RFC 3339 datetimes, arrays, and inline tables — all with explicit syntax. It does not coerce types. The string "true" is always a string; the boolean true is always a boolean. The Norway problem cannot happen here.
Rust's Cargo.toml popularized TOML in the broader developer community, and Python's ecosystem followed with pyproject.toml (PEP 518 in 2016). By 2024, over 60% of new Python packages published to PyPI shipped a pyproject.toml. TOML's indentation-free section headers ([section] and [[array-of-tables]]) make large configs scannable without mentally tracking whitespace depth.
The main limitation is verbosity on deeply nested structures — a five-level hierarchy that reads cleanly in YAML becomes a chain of dotted section headers in TOML. It also lacks an equivalent to YAML anchors, so repeated values must be duplicated or handled at the application layer.
Explore how TOML structures map to their JSON equivalents using our TOML to JSON Converter.
Best fit: Rust projects (Cargo.toml), Python packages (pyproject.toml), application runtime config, any file a human will edit regularly.
HCL: Declarative Configuration for Infrastructure
HCL (HashiCorp Configuration Language) is purpose-built for infrastructure-as-code. Terraform, Packer, Nomad, and Vault all use it. HCL 2, released with Terraform 0.12 in 2019, is a significant rewrite featuring a stricter type system, first-class expression evaluation, and cleaner variable interpolation.
resource "aws_instance" "web" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-server"
Environment = "production"
}
}
variable "region" {
type = string
default = "us-east-1"
}
locals {
common_tags = {
Project = "my-api"
Owner = "platform-team"
}
}
HCL is not a general-purpose config format. Its power comes from infrastructure-specific features: block-based resource definitions, a built-in function library (cidrsubnet(), toset(), join(), templatefile()), for expressions for dynamic resource generation, and cross-block references that let Terraform build a dependency graph automatically.
One important distinction: HCL is declarative, not imperative. There are no mutable variables, no traditional loops, and no sequential execution. You describe desired state; the provider reconciles the current state to match. This constraint is a feature for infrastructure provisioning but makes HCL impractical for application config where runtime logic matters.
Our YAML to HCL Converter and JSON to HCL Converter can help when migrating existing config files into a Terraform-managed infrastructure workflow.
Best fit: Terraform, Packer, Nomad, Vault, and any HashiCorp-ecosystem tooling. Not a replacement for JSON, YAML, or TOML in application-layer config.
Side-by-Side: The Same Config in Four Formats
To make the differences concrete, here is an identical database configuration written in all four formats.
JSON
{
"database": {
"host": "db.internal",
"port": 5432,
"name": "production",
"pool": { "min": 2, "max": 10 }
}
}
YAML
database:
host: db.internal
port: 5432
name: production
pool:
min: 2
max: 10
TOML
[database]
host = "db.internal"
port = 5432
name = "production"
[database.pool]
min = 2
max = 10
HCL
database {
host = "db.internal"
port = 5432
name = "production"
pool {
min = 2
max = 10
}
}
The YAML version is the most concise at 7 lines of content. The TOML version is the most explicit about value types and section boundaries. The JSON version is the most portable across parsers. The HCL version is idiomatic for infrastructure tooling but the least natural for application-layer config.
When to Use Each Format: A Decision Guide
Let the toolchain decide when it has a strong preference. When it does not, use this rubric.
| Scenario | Recommended Format |
|---|---|
| npm / Node.js package metadata | JSON (package.json) |
| Kubernetes manifests | YAML |
| GitHub Actions workflows | YAML |
| Docker Compose | YAML |
| Rust package config | TOML (Cargo.toml) |
| Python package config | TOML (pyproject.toml) |
| Application runtime config, human-edited | TOML |
| Terraform / Packer / Nomad | HCL |
| REST API responses | JSON |
| Machine-generated config consumed by code | JSON |
| Config requiring comments, no toolchain lock-in | TOML |
Use JSON when a machine writes the file, a schema must validate it, or you are crossing a language or service boundary. JSON's zero-ambiguity parse and universal library support make it the right choice for interchange.
Use YAML when the toolchain mandates it or your team is already invested in it. Do not choose YAML over TOML for new greenfield application config — the spec complexity and silent failure modes are not worth it unless you are locked into a YAML-native tool.
Use TOML when humans write the file, no toolchain forces a different format, and you want a type-safe config without YAML's footguns. TOML is the right default for most new application config files written in 2024 and beyond.
Use HCL when you are in the HashiCorp ecosystem. Do not reach for it outside Terraform and its siblings — it is specialized enough that the learning cost is only worth it if you are working with those tools directly.
Common Pitfalls to Avoid
YAML type coercion: Quote any string that looks like another type — "true", "yes", "on", "no", country codes, version numbers composed only of digits like "1.0". If you are using PyYAML, always call yaml.safe_load(), never the plain yaml.load() — the full loader allows arbitrary Python object deserialization, which is a remote code execution vector if the input is not fully trusted.
JSON comments: Standard JSON parsers reject // and /* */ comments per the RFC. If you need comments in a JSON-like format, VS Code's jsonc (JSON with Comments) or the json5 format extend the syntax, but be explicit about which parser your toolchain uses. Feeding a jsonc file to a standard JSON parser will throw.
TOML datetime round-trips: TOML supports RFC 3339 datetimes natively (updated_at = 2024-01-15T09:30:00Z). This is a feature, but when your library deserializes the datetime into a native date object and you serialize back to TOML, confirm the output format matches what other consumers expect. Not all libraries preserve timezone offsets correctly on the round-trip.
HCL vs. JSON Terraform configs: Terraform accepts both .tf (HCL) and .tf.json (JSON) files in the same directory. JSON Terraform configs are useful for programmatically generated infrastructure but are far less readable than their HCL equivalents. Mixing both formats in the same Terraform module is legal but creates maintenance confusion — standardize on one per module.
Conclusion
JSON, YAML, TOML, and HCL each emerged from a distinct set of constraints and continue to serve different masters. JSON wins on portability and machine consumption. YAML dominates the DevOps tooling ecosystem despite a specification complex enough to warrant a dedicated book. TOML offers the best balance of human-writability and type safety for application config with none of the coercion surprises. HCL is purpose-built for declarative infrastructure and belongs in the Terraform world.
The practical rule: let the toolchain choose when it has a preference, and default to TOML when it does not. Avoid introducing YAML for new application config unless your team is already deep in it — the Norway problem, silent indentation failures, and 86-page spec are real costs that compound over time. When you need to move between formats, our JSON to YAML Converter, TOML to JSON Converter, YAML to HCL Converter, and JSON to HCL Converter handle the structural translation without the manual reformatting headache.
Try these free tools
Free & private — all tools run in your browser, nothing uploaded.