Every application needs configuration, and every team argues about how to store it. The configuration format landscape in 2026 includes at least six serious contenders: INI, JSON, YAML, TOML, ENV/dotenv, and HCL. Each has a sweet spot, a community, and a set of tradeoffs that its advocates rarely mention.

This guide compares all six formats on the dimensions that matter for configuration: readability, type safety, comment support, nesting depth, ecosystem alignment, and the specific failure modes each format invites. The goal is a clear decision framework so you can pick a format in five minutes and move on to building features.

INI: Simple, Unspecified, Legacy

[server]
host = localhost
port = 8080
debug = true

[database]
url = postgres://localhost/mydb
pool_size = 25

INI files are the grandfather of configuration formats. They originated in MS-DOS and Windows (win.ini, system.ini) and remain in use through php.ini, MySQL's my.cnf, pip.conf, and many legacy systems.

INI's fundamental problem: there is no specification. Different parsers handle edge cases differently:

  • Are keys case-sensitive? (Windows: no. Python's configparser: no. Most Unix tools: yes.)
  • Can values span multiple lines? (Some parsers: yes with continuation characters. Others: no.)
  • What's the comment character? (; in Windows, # in Unix, both in some parsers.)
  • Can sections nest? (Standard: no. Some tools support dotted section names: [section.subsection].)
  • Are there types? (No. Everything is a string. port = 8080 is the string "8080".)

INI works for trivially simple configs (key-value pairs grouped into sections). For anything more complex, use TOML — it's essentially INI with a specification, types, and nesting. Converting INI to TOML is straightforward because TOML's table syntax is deliberately similar to INI's section syntax.

JSON: Strict, Universal, Comment-less

{
  "server": {
    "host": "localhost",
    "port": 8080,
    "debug": true
  },
  "database": {
    "url": "postgres://localhost/mydb",
    "pool_size": 25
  }
}

JSON is the most widely supported data format but a mediocre configuration format. Its type system (strings, numbers, booleans, null, arrays, objects) is an advantage over INI, and its strict syntax prevents ambiguity. But three missing features make it frustrating for human-edited configs:

  1. No comments. You can't explain why a value is set, document valid ranges, or comment out a line while debugging. This alone disqualifies JSON for most config use cases.
  2. No trailing commas. Adding or removing the last item in an array or object requires editing two lines, creating noisy diffs.
  3. Verbose for nesting. Braces and quotes add visual noise. A 20-line YAML config becomes a 30-line JSON config.

JSON for config works when: the config is machine-generated and machine-consumed (build artifacts, lock files), the ecosystem mandates it (package.json, tsconfig.json), or you need guaranteed parse consistency across all tools.

JSONC (VS Code's settings.json) and JSON5 add comments and trailing commas but aren't universally supported. Converting JSON to YAML or TOML lets you add comments and improve readability.

YAML: Expressive, Risky, Dominant in DevOps

# Server configuration
server:
  host: localhost
  port: 8080
  debug: true
  ssl:
    enabled: true
    cert: /etc/ssl/cert.pem

# Database settings
database:
  url: postgres://localhost/mydb
  pool_size: 25

YAML is the most feature-rich configuration format: comments, multiline strings, anchors for DRY configs, deep nesting, and a broad type system. It's the mandatory choice for Kubernetes, Docker Compose, GitHub Actions, Ansible, and most infrastructure tooling.

YAML's risks for configuration:

  • Implicit type coercion turns NO into false, 3.10 into 3.1, and on into true. Quote strings defensively.
  • Indentation errors create valid-but-wrong YAML. A misaligned key silently changes the config structure.
  • Tabs are illegal for indentation, but invisible to most editors.
  • Specification complexity (86 pages) means different parsers may behave differently on edge cases.

Use YAML when the tool requires it or when your config has deep nesting (4+ levels). For simpler configs, TOML avoids YAML's footguns while keeping comments and readability.

TOML: Explicit, Safe, Growing

# Server configuration
[server]
host = "localhost"
port = 8080
debug = false

[server.ssl]
enabled = true
cert = "/etc/ssl/cert.pem"

# Database settings
[database]
url = "postgres://localhost/mydb"
pool_size = 25

TOML is designed specifically for configuration. It has everything JSON lacks (comments, native date types, multiline strings) without YAML's footguns (no implicit type coercion, no indentation rules, 30-page spec vs. 86).

TOML is the standard for Rust (Cargo.toml), Python (pyproject.toml), Hugo, and Netlify. Its adoption is accelerating as newer ecosystems choose it over YAML for application-level config.

TOML limitations: deep nesting is verbose (long dotted table headers), arrays must be homogeneous (no mixed types), and no null type. These are non-issues for typical config files but matter if your config has complex structure.

For new projects where you control the config format, TOML is the recommended default. Convert from YAML or JSON if migrating existing configs.

ENV / Dotenv: Secrets and 12-Factor Apps

# .env file
DATABASE_URL=postgres://localhost/mydb
REDIS_URL=redis://localhost:6379
API_KEY=sk-live-abc123def456
DEBUG=true
PORT=8080

ENV files (commonly called dotenv files, from the .env convention) are the simplest possible format: KEY=VALUE pairs, one per line. They align with the 12-Factor App methodology, which stores config in environment variables for portability across deployment environments.

ENV characteristics:

  • No types. Everything is a string. Your application must parse "8080" to an integer and "true" to a boolean.
  • No nesting. Flat key-value only. Convention uses underscores for hierarchy: DATABASE_POOL_SIZE=25.
  • No arrays. Convention uses comma-separated strings: ALLOWED_ORIGINS=https://a.com,https://b.com.
  • Comments with #. Lines starting with # are ignored.
  • Quoting varies: some libraries support KEY="value with spaces", others don't.

ENV files are best for: secrets (API keys, database passwords), deployment-specific values (URLs, ports, feature flags), and anything that changes between environments. They should not be your primary config format — use them alongside a structured config file (TOML, YAML) that holds non-secret, non-environment-specific settings.

Convert ENV to JSON or YAML when you need to process environment variables as structured data. Convert JSON to ENV to flatten a structured config into environment variables for deployment.

HCL: Terraform's Domain-Specific Language

resource "aws_instance" "web" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = "t2.micro"

  tags = {
    Name = "WebServer"
  }

  ebs_block_device {
    device_name = "/dev/sdf"
    volume_size = 50
  }
}

HCL (HashiCorp Configuration Language) is a domain-specific language created for Terraform, Vault, Consul, Nomad, and other HashiCorp tools. It's neither JSON nor YAML — it's a purpose-built language with types, expressions, functions, loops, and conditionals.

HCL is not a general-purpose config format. You wouldn't use it for application settings or CI/CD pipelines. But within the HashiCorp ecosystem, it's the only format that fully supports Terraform's features (expressions, modules, data sources, provisioners). HCL files can be converted to JSON (terraform show -json) for processing, but the JSON representation lacks HCL's expressiveness.

Complete Comparison

FeatureINIJSONYAMLTOMLENVHCL
Comments# or ;No####, //, /* */
TypesNone (strings only)6 types11+ types10 types (incl. dates)None (strings only)Rich (incl. expressions)
Nesting1 level (sections)UnlimitedUnlimitedUnlimited (verbose past 3)NoneUnlimited
ArraysNo standardYes (heterogeneous)Yes (heterogeneous)Yes (homogeneous only)Convention onlyYes
Spec existsNoRFC 8259YAML 1.2TOML v1.0.0No formal specSpec by HashiCorp
Parse safetyLow (no spec)High (strict syntax)Medium (coercion risks)High (explicit types)High (trivial format)High (typed)
EcosystemPHP, MySQL, Python, WindowsNode.js, TS, .NET, webK8s, Docker, CI/CD, AnsibleRust, Python, Hugo, Go12-factor, Docker, all langsTerraform, Vault, Consul

Decision Framework: Which Format for Your Config?

Follow this flowchart:

  1. Is the format mandated by a tool? (package.json, docker-compose.yml, Cargo.toml) Use what's required.
  2. Is it secrets or environment-specific settings? Use ENV. Never commit secrets to a config file that's in version control.
  3. Is it infrastructure config with deep nesting? (Kubernetes, CI/CD) Use YAML.
  4. Is it Terraform/Vault/Consul? Use HCL.
  5. Is it application config with moderate structure? Use TOML. It's the safest, simplest choice for general config files.
  6. Is it machine-generated config that code reads? Use JSON. Strict syntax, universal parsers.

In practice, most projects use multiple formats: .env for secrets, TOML or YAML for application config, and whatever the infrastructure tools require.

Migration Paths Between Formats

FromToConversionWhat Changes
INITOMLINI to TOMLGain: types, nested tables, spec compliance. Strings now need quotes.
JSONYAMLJSON to YAMLGain: comments, readability. Risk: YAML type coercion on unquoted values.
JSONTOMLJSON to TOMLGain: comments, native dates. Works well for shallow JSON. Deep nesting becomes verbose.
YAMLTOMLYAML to TOMLGain: explicit types, no indentation bugs. Lose: anchors, deep nesting elegance, null.
ENVJSONENV to JSONGain: types, nesting. All values start as strings — manual type annotation needed.
ENVYAMLENV to YAMLSame as ENV to JSON, with comments.
TOMLENVTOML to ENVLose: types, nesting, arrays. Only flat key-value pairs survive.

All format conversions lose comments. After converting, re-add comments manually. This is the single most annoying aspect of config format migration.

The config format choice usually makes itself. Your tools dictate most of the decision (package.json, docker-compose.yml, Cargo.toml). For the config files you control, the recommendation is simple: ENV for secrets, TOML for application config. If your config requires deep nesting or your team is fluent in YAML, use YAML. If the config is machine-generated, use JSON.

The worst choice is no choice — ending up with a mix of undocumented formats because each developer used their favorite. Pick a format, document the convention, and make sure everyone on the team can read it.