INI Files: The Oldest Config Format Still in Use

INI Files: The Oldest Config Format Still in Use

INI files are older than most of the developers using them. They predate XML, predate JSON, predate YAML by decades. And yet here we are in 2026 and you'll still find .ini files in fresh codebases, Git repos, and server configs everywhere. There's a reason for that.

Where INI Came From

The format goes back to Windows 3.1, where Microsoft used .ini files for per-application settings. WIN.INI and SYSTEM.INI configured the whole operating system. The format was chosen for its simplicity: a plain text file any text editor could open, readable without documentation, writable by hand without tooling.

Similar formats existed on DOS systems before Windows 3.1. The convention was already well-established by the time it got the "INI" name — and that format has been in continuous use for over 35 years.

Windows NT and later moved toward the registry for system settings, but .ini files stuck around for application-level config. They're also the basis for formats that came later — Git's config format, .editorconfig, Python's configparser module. The lineage is direct.

The Structure: Sections, Keys, and Values

An INI file has three building blocks.

Sections are declared with a name in square brackets. Everything below a section header belongs to that section, until the next section header or end of file.

Keys (sometimes called "properties" or "options") are names on the left side of an equals sign. Values are on the right. Whitespace around the = is typically ignored.

Comments start with a semicolon ; or a hash #, depending on the parser. Everything after the comment character on that line is ignored.

; Database connection settings
[database]
host = localhost
port = 5432
name = myapp_production
pool_size = 10

[cache]
backend = redis
url = redis://localhost:6379/0
ttl = 3600

[logging]
level = info
# Some parsers treat # as comments too
file = /var/log/myapp.log

That's essentially the whole format. No arrays, no nested objects, no type system. Just sections, keys, and string values.

The Problem: There Is No Standard

INI has no formal specification. No RFC, no schema validator, no authoritative grammar. Every parser implements its own dialect — and that trips up developers coming from JSON or YAML.

Consider a few questions that different parsers answer differently:

Are keys outside any section valid? Some parsers treat them as belonging to a default section. Others throw a parse error.

Are duplicate keys allowed? Some parsers take the last value. Some take the first. Some merge them into a list.

Are values case-sensitive? Keys often get lowercased automatically. Values might or might not.

What's the comment syntax? Python's configparser supports both ; and #. Windows APIs only recognized ;. Git's INI parser uses # and ;.

Can you quote values? Some parsers strip quotes from value = "something", treating it as the string something. Others keep the quotes as part of the value.

If you're reading INI files written by other tools, test it. Don't assume your parser and theirs agree on edge cases.

Python's configparser

Python ships with configparser in the standard library, and it's the most commonly used INI parser in modern code. The defaults are sensible, and it handles the common dialect well.

import configparser

config = configparser.ConfigParser()
config.read('settings.ini')

# Access values
host = config['database']['host']
port = config.getint('database', 'port')  # type coercion
ttl = config.getfloat('cache', 'ttl')

# Default values
log_level = config.get('logging', 'level', fallback='warning')

# Write a config file
config['app'] = {
    'version': '2.1.0',
    'debug': 'false'
}
with open('output.ini', 'w') as f:
    config.write(f)

One quirk: configparser stores all keys in lowercase by default. If your INI file has MaxConnections = 100, you read it as config['section']['maxconnections']. Override this with a custom optionxform if you need case-sensitive keys.

Python's configparser also supports interpolation — referencing other values within the file using %(key)s syntax:

[paths]
base = /opt/myapp
logs = %(base)s/logs
data = %(base)s/data

This is a configparser-specific feature, not universal INI behavior.

Git Config Is INI

If you've used Git, you've used INI without necessarily knowing it. The .git/config file, ~/.gitconfig, and /etc/gitconfig are all INI format.

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false

[remote "origin"]
    url = git@github.com:user/repo.git
    fetch = +refs/heads/*:refs/remotes/origin/*

[branch "main"]
    remote = origin
    merge = refs/heads/main

Git's INI dialect supports subsections — the "origin" part in [remote "origin"] is a quoted subsection name. That's not universal INI behavior, it's Git-specific. But it shows how parsers can extend the base format without breaking the overall readability.

.editorconfig Is INI Too

.editorconfig files — used to enforce consistent coding style across editors and IDEs — also use INI syntax. Section names are glob patterns instead of arbitrary names, but the format reads identically.

root = true

[*]
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

[Makefile]
indent_style = tab

Two completely different tools — Git and EditorConfig — independently chose INI format. That says something about its staying power. It's readable, it's writable, and every developer can understand it on first sight.

Multiline Values

Standard INI doesn't have a native multiline syntax, but many parsers support continuation lines — a value that spans multiple lines by indenting the continuation.

Python's configparser does this:

[email]
recipients =
    alice@example.com
    bob@example.com
    charlie@example.com

The value of recipients becomes a single string with newlines. You'd split it yourself to get the list. Again, this is parser-specific behavior, not universal INI.

INI vs TOML vs YAML

When should you use INI over the alternatives?

INI wins when the config is truly flat — a handful of sections, simple key-value pairs, edited by humans unfamiliar with markup syntax. Git config and .editorconfig are perfect examples. Non-technical users can edit an INI file without training. The format has no footguns.

TOML wins when you need types. TOML has booleans, integers, floats, dates, arrays, and inline tables, all with explicit syntax. You don't have to manually coerce "3600" to an integer. TOML is where INI-style configs logically go when they grow up. Read TOML: The Config Format Built to Replace INI for the full comparison.

YAML wins when you need deeply nested structures or you're already in a YAML-heavy ecosystem (Kubernetes, Ansible, GitHub Actions). YAML's expressiveness is unmatched — and so is its potential for confusion. For simple configs, YAML is overkill.

The honest answer is: if your config file is going to stay flat, INI is perfectly fine. If it's going to grow nested structures or typed values, start with TOML.

When INI Is Still the Right Call

Some teams reach for JSON or YAML for configs where INI would work better. INI is worth choosing when:

  • The file will be edited by hand frequently, by people who aren't developers
  • The config is genuinely flat — no nesting needed
  • You want zero external dependencies (INI parsers are in every standard library)
  • Compatibility with an existing ecosystem using INI (Git hooks, .editorconfig, PHP php.ini, MySQL my.cnf)
  • You want comment support in your config (JSON doesn't have it without a superset)

The format's longevity is a feature, not a liability. A config format that non-programmers can read and edit without a tutorial has real value.

Wrapping Up

INI files are simple by design, and that simplicity is why they've outlasted dozens of "better" alternatives. No spec, no schema validator — just sections and keys. When you need to understand or debug a config in this format, the JSON Formatter can help you work through related structured data, and the JSON to YAML converter is useful if you're migrating a flat INI config to a more structured format. The configparser docs are the definitive reference for how Python handles INI parsing.