File types · Explainer

The INI file (.ini)

An INI file is a plain-text configuration file made of [sections] and key = value lines, one of the oldest and simplest settings formats around. This guide explains what's inside one, why different programs read the same file differently, the encoding traps, and has free sample INI files to download for testing.

At a glance

Full name
Initialization file
Extensions
.ini; INI-style files also use .cfg, .conf, .cnf and fixed names like .gitconfig
MIME type
None registered; servers usually send text/plain
Kind
Plain text (configuration data)
Standard
None. Each program's parser defines its own dialect
Origin
MS-DOS and early Windows, which kept settings in files such as WIN.INI and SYSTEM.INI
Data types
None: every value is text until the program converts it
Comments
; almost everywhere; # in many dialects, but not all
Encoding
Not defined. UTF-8 is the safe choice today; Windows tools may add a byte order mark
Opens with
Any text editor
Magic number
None

The basics

What is an INI file?

An INI file stores settings as text: section names in brackets, and under each one, lines of key = value. The name comes from "initialization": programs read the file when they start. It's easy to read and edit by hand, which is why the format has survived since the MS-DOS era.

You'll find INI files, or files in the same style with another extension, all over the place:

FileRead by
php.iniPHP, for every setting from memory limits to upload sizes
desktop.iniWindows Explorer, for a folder's icon and display settings; hidden by default
my.cnf, my.iniMySQL and MariaDB option files
setup.cfg, tox.ini, pytest.iniPython tools, through the configparser dialect
.gitconfig, .git/configGit, in its own INI-like dialect
.npmrcnpm, parsed with the ini package
.editorconfigEditors that follow EditorConfig
*.desktop, *.serviceLinux desktop launchers and systemd units, both INI-style
Game and app settingsMany Windows programs and games still keep options in an .ini file next to the program or in the user's AppData folder
  • Not a standard. There's no specification, so each program decides what counts as a comment, whether quotes mean anything and what happens to duplicates.
  • Not typed. Every value is text. Whether false, 0 or off means "no" is up to the program.
  • Not nested. Two levels, sections and keys. Anything deeper is a convention of one particular parser.

Anatomy

What's inside an INI file

An INI file has two levels: sections, named in square brackets, and keys with values inside them. That's the whole common core; everything else depends on the program reading it. Here is a small file, part by part.

settings.ini
; Settings for a small web app
[app]
name = Sample web app
debug = false
port = 8080

[database]
host = db.example.internal
port = 5432
password =

# Paths need no escaping
[paths]
data_dir = C:\ProgramData\SampleApp

[mail server]
smtp.host = mail.example.internal
  1. 1; Settings for a small web app

    A comment

    A line starting with a semicolon is a comment in every INI dialect.

  2. 2[app]

    A section header

    A name in square brackets. Every key below it, up to the next header, belongs to this section. Sections can't be nested.

  3. 3name = Sample web app

    A key and a value

    Everything after the =, minus surrounding spaces, is the value. No quotes are needed for text with spaces.

  4. 4debug = false

    A value that looks like a boolean

    In INI it's the text false. Python's configparser keeps it as text until you call getboolean(); the ini package for Node.js returns the boolean false; PHP returns an empty string.

  5. 5port = 8080

    A number, as text

    Also text, converted by the program. port can appear again in another section: keys only have to be unique within their section.

  6. 6password =

    An empty value

    Accepted by most parsers, which return an empty string. A key with no = at all is another matter: some parsers accept it, others report an error.

  7. 7# Paths need no escaping

    A hash comment

    Python, Git and the ini package treat # as a comment too, but PHP's parser doesn't. Stick to ; in files that different programs read.

  8. 8data_dir = C:\ProgramData\SampleApp

    A Windows path

    Backslashes are ordinary characters in most dialects. Git's config format is an exception, where a backslash starts an escape.

  9. 9[mail server]

    A section name with a space

    Allowed by most parsers. Git's config format isn't one of them: it writes subsections as [remote "origin"] instead.

  10. 10smtp.host = …

    A dotted key

    To most parsers the dot is just part of the name. A few, such as the ini package for section names, read dots as nesting.

Background

Where INI files come from

INI files became widespread with MS-DOS and 16-bit Windows. Up to Windows 3.1x, Windows itself kept its configuration in INI files such as WIN.INI and SYSTEM.INI, and programs read their own with Windows API functions like GetPrivateProfileString. Those functions look up a value by section and key, ignoring upper and lower case.

From Windows 95 and Windows NT on, Microsoft moved system settings into the registry. The INI functions still exist, but Microsoft's documentation now says they are provided only for compatibility with 16-bit applications and that applications should store settings in the registry. Windows even redirects some INI files to the registry through the IniFileMapping registry key, so editing those files has no effect.

Outside Windows the format lived on because it's so simple. Python, PHP, Git, systemd and many others adopted INI-style files, each with its own parser and its own small differences, which is where most of today's confusion comes from.

Quirks

One file, four different readings

sample-parser-differences.ini contains eight ordinary-looking lines. Python's configparser, the ini package for Node.js and PHP's parse_ini_file all read it without an error, and all return something different:

Tested with Python 3.13 configparser (default settings), ini 6.0.0 and PHP 8.3
LinePython configparserini (Node.js)PHP parse_ini_file
port = 8080 ; the default"8080 ; the default""8080""8080"
path = /var/www # web root"/var/www # web root""/var/www""/var/www # web root"
banner = " Welcome "Quotes kept: " Welcome "" Welcome "" Welcome "
enabled = true"true" (text)true (boolean)"1"
Timeout = 30Key becomes timeoutKey stays TimeoutKey stays Timeout
discount = 15%%"15%""15%%""15%%"
tags[] = webA key called tags[]A list: ["web"]A list: ["web"]
[app.cache]A section called app.cacheNested: app → cacheA section called app.cache

Git's git config reads the first six lines (strips both inline comments and the quotes, lowercases Timeout) and then stops with bad config line, because tags[] isn't a valid Git key. The other differences that matter in practice:

  • Duplicate keys. Python's configparser stops with DuplicateOptionError by default (and because it lowercases keys, Port and port count as duplicates). The ini package and PHP keep the last value. Git keeps all of them as a multi-valued key.
  • Keys before the first section. configparser reports MissingSectionHeaderError unless you pass allow_unnamed_section=True (Python 3.13 and later). The ini package and PHP put them at the top level.
  • Reserved words and characters in PHP. null, yes, no, true, false, on, off and none can't be keys. In unquoted values, characters such as &, |, ~ and ! have special meanings, and some, such as = and (, are syntax errors.
  • Multi-line values. configparser continues a value on indented lines below it. The ini package reads those lines as new keys, and PHP ignores them.

Writing INI files that every parser reads the same way

Use ; comments on their own lines, one key per name per section, a section header before the first key, no quotes, no inline comments and only letters, digits, _, - and . in names. sample-config.ini follows these rules.

Characters

Encoding, byte order marks and line endings

Because there's no standard, nothing says which encoding an INI file uses. Old Windows programs read and write them in the system's ANSI code page; newer software expects UTF-8. Three things go wrong regularly:

  • A byte order mark (BOM). Some Windows editors put three invisible bytes, EF BB BF, at the start of a UTF-8 file. Python's configparser, reading with encoding="utf-8", then sees \ufeff[greetings] instead of a section header and stops with MissingSectionHeaderError; reading with encoding="utf-8-sig" works. The ini package is worse: it raises no error but turns the first section header into a key, so the first section's settings end up at the top level. PHP and Git ignore the BOM.
  • The wrong code page. Without an explicit encoding, Python opens the file with the system's default encoding, which on many Windows machines is not UTF-8. Accented text then comes out as é. Always pass the encoding.
  • Line endings. All the parsers tested here accept Windows (CRLF) and Unix (LF) line endings.

sample-utf8-bom-crlf.ini has a BOM, CRLF line endings and non-ASCII keys and values, so you can see which of these problems a tool has.

How it's used

Reading an INI file from a program

read_settings.py
import configparser

config = configparser.ConfigParser()
config.read("settings.ini", encoding="utf-8-sig")  # also accepts a BOM

port = config.getint("database", "port")            # "5432" -> 5432
debug = config.getboolean("app", "debug")           # "false" -> False
host = config.get("database", "host", fallback="localhost")
  • Python: configparser in the standard library. Values are strings; getint(), getfloat() and getboolean() convert them, and getboolean() accepts 1/0, yes/no, true/false and on/off. Keys in a [DEFAULT] section apply to every section, and %(name)s refers to another key, which is why a literal % must be written %%.
  • Node.js: the ini package. It returns true, false and null as real values, collects key[] lines into arrays and nests dotted section names.
  • PHP: parse_ini_file($path, true) keeps sections. By default on, yes and true become "1"; pass INI_SCANNER_TYPED to get real booleans, null and integers.
  • Windows (C, C++, C#): GetPrivateProfileString and related functions still work, case-insensitively, but Microsoft recommends the registry or a modern format for new code.
  • Git-style files: git config -f file.ini section.key reads a value from any file in Git's dialect.

Comparison

INI vs TOML, YAML and JSON

INI[TOML](/file-types/toml)[YAML](/file-types/yaml)[JSON](/file-types/json)
SpecificationNoneYes: TOML 1.1.0Yes: YAML 1.2.2Yes: RFC 8259
Looks like[section] and key = value[table] and key = valueIndented key: valueBraces and quotes
TypesEverything is textStrings, numbers, booleans, dates, arraysGuessed from the textStrings, numbers, booleans, null, arrays, objects
NestingTwo levelsAny depthAny depthAny depth
Comments; and often ###None
Same result in every parserNoYesMostly, if you stick to YAML 1.2Yes

If you're choosing a format for a new project and like the look of INI, TOML is the closest thing with a specification: a simple INI file with # comments and quoted string values is often already valid TOML. Keep INI where a program expects it, such as php.ini or desktop.ini.

Troubleshooting

Why won't my INI file load?

MessageRead byUsual cause and fix
MissingSectionHeaderError: File contains no section headers.PythonKeys before the first [section], or a BOM. Add a header, or read with encoding="utf-8-sig"
DuplicateOptionError: … option 'port' in section 's' already existsPythonThe same key twice in a section, including Port and port. Remove one, or pass strict=False to keep the last
InterpolationSyntaxError: '%' must be followed by '%' or '('PythonA literal % in a value. Write %%, or create the parser with interpolation=None
ParsingError: Source contains parsing errorsPythonOften a line with a key but no =. Add = value, or pass allow_no_value=True
syntax error, unexpected '=' (or '(', '!')PHPA special character in an unquoted value. Put the value in double quotes
syntax error, unexpected BOOL_TRUE (or NULL_NULL)PHPA reserved word such as on or null used as a key. Rename the key
fatal: bad config line 21GitA name Git doesn't allow, such as one with a space or [], or an invalid backslash escape
Settings are ignored, no errorAnyThe key is in the wrong section, the program reads a different file (php --ini lists the files PHP loads), or the program must be restarted

How to

How to open and edit an INI file

  • Windows: right-click and choose Open with, then Notepad or another text editor. Some .ini files sit in protected folders such as Program Files; open the editor as administrator to save changes there.
  • macOS and Linux: open it in TextEdit (in plain-text mode), VS Code or any terminal editor such as nano.
  • desktop.ini: it's a hidden system file, so Explorer only shows it when "Hide protected operating system files" is turned off in Folder Options.
  • php.ini: php --ini prints which file PHP loaded. After editing it, restart PHP-FPM or the web server.
  • Convert to JSON: load it with a parser and write JSON, but remember the result depends on the parser, as the table above shows.

Security

Is an INI file safe? Passwords in config files

An INI file is only text and can't run anything when opened. The risk is what's written in it: INI files often hold credentials in plain text.

  • Database passwords in my.cnf or an app's config.ini. Restrict who can read the file, such as chmod 600 on Linux and macOS.
  • Tokens in .npmrc (registry auth tokens) and in .git/config when a remote URL includes a username and token.
  • Files inside the web root. A config.ini in a public folder can be downloaded by anyone who guesses its name. Keep config files outside the folder the web server publishes, or block .ini files in the server's configuration.
  • Shared copies. Before posting an INI file in a forum, ticket or link, remove passwords, keys and internal hostnames.

Never commit a real config.ini with secrets to a repository. Commit an example file with placeholders, such as config.example.ini, and keep the real one out with .gitignore.

Free downloads

Sample INI files to download

Free sample INI files for testing parsers, config loaders, encodings and upload limits. Each one loads in Python's configparser (the BOM file with encoding="utf-8-sig"), the ini package for Node.js and PHP's parse_ini_file.

sample-config.ini · 1.3 KBDownloadOpen
Loading…

What's inside sample-config.ini

The file is commented: each numbered comment marks one of these parts. It sticks to the INI that Python, Node.js and PHP read alike.

  1. Comment 1: comments

    Only ; comments, on their own lines, because PHP doesn't treat # as a comment.

  2. Comments 2 and 3: sections and keys

    A [app] header and key = value lines, with spaces around = that every parser trims.

  3. Comments 4 and 5: values are text

    debug = false and an empty owner =. The parsers agree on the empty value but not on false: text in Python, a boolean in Node.js, an empty string in PHP.

  4. Comment 6: keys per section

    port appears in both [app] and [database], which is fine: keys only need to be unique within a section.

  5. Comments 7 and 8: punctuation and backslashes

    A value with a colon, a comma and a slash, and a Windows path with backslashes, all without quotes.

  6. Comment 9: lists

    Comma-separated values, which your program has to split itself.

  7. Comment 10: names with spaces and dots

    [mail server] and smtp.host, accepted by Python, Node.js and PHP. Git's config reader rejects them, and also stops earlier, at the backslashes in comment 8.

All sample INI files

  • Commented config file

    sample-config.ini · 1.3 KB (1,368 bytes)

    An app configuration with 10 numbered comments: sections, keys, empty values, a Windows path, comma-separated lists and names with spaces and dots, written so that configparser, ini and PHP read the same strings.

    • Teaching the parts of an INI file
    • Testing config loaders
  • Where parsers disagree

    sample-parser-differences.ini · 771 bytes

    Inline ; and # comments, a quoted value, true, a capitalised key, %%, a key[] list and a dotted section name. Parsers read it without errors and return the different results in the table above.

    • Finding out which dialect your parser follows
    • Comparing parsers across languages
  • UTF-8 with BOM, CRLF and non-ASCII text

    sample-utf8-bom-crlf.ini · 602 bytes

    Starts with a UTF-8 byte order mark directly before the first [section], uses Windows line endings, and has greetings in six scripts, emoji, and accented section and key names.

    • Testing BOM handling
    • Spotting encoding bugs
  • php.ini-style settings

    sample-php-style.ini · 900 bytes

    Real PHP directive names with example values in [PHP] and [Session] sections: size suffixes, On/Off switches and quoted values with special characters.

    • Testing tools that read or edit `php.ini`
    • Seeing PHP's quoting rules and `On`/`Off` conversion
  • 100 KB INI file

    sample-100kb.ini · 100 KB (102,400 bytes)

    1,042 sections of fictional device settings, padded with a final comment to exactly 102,400 bytes.

    • Upload and size-limit tests
    • Parser performance

Ways to test with them

  • Which dialect a parser follows: load sample-parser-differences.ini and compare port, banner and enabled with the table above.
  • BOM handling: load sample-utf8-bom-crlf.ini. A correct reader returns a greetings section with de = Grüße. A [greetings] key at the top level, or a missing-section error, means the BOM wasn't stripped.
  • PHP's rules: parse_ini_file turns display_errors = Off in sample-php-style.ini into an empty string and log_errors = On into "1".
  • Upload limits: sample-100kb.ini is exactly 102,400 bytes, for testing a limit at the boundary.

Free to use for testing, teaching and demos, commercially or not, with no attribution needed. A link back to this page is always appreciated.

Put it online

Have an INI file? Give it a link.

Drop an INI file on 99helpers and it gets its own address, for sharing a config example or a test fixture. Opened in a browser, the link shows the file as plain text. Remove passwords and keys before you share it.

Questions

INI files, frequently asked

What is an INI file?

A plain-text settings file with [sections] and key = value lines. Programs read it at start-up; the name comes from "initialization".

How do I open an INI file?

With any text editor: Notepad on Windows, TextEdit in plain-text mode on a Mac, or VS Code anywhere. Double-clicking usually opens it in Notepad on Windows.

Is it safe to delete desktop.ini?

Yes, it only stores how a folder looks, such as its icon or display name. Windows may recreate it when you change those settings again.

Can INI files have comments?

Yes. A line starting with ; is a comment in every dialect. Many parsers also accept #, but PHP's doesn't, and comments after a value on the same line are handled differently by different parsers.

Are INI keys case-sensitive?

It depends on the parser. Windows' own functions and Python's configparser ignore case in keys; the ini package for Node.js and PHP don't. Git ignores case in section and key names but not in subsection names.

Is there an INI standard?

No. The format grew out of MS-DOS and Windows, and every parser has its own rules for comments, quotes, duplicates and encoding. TOML is a newer format in the same style that does have a specification.

How do I read an INI file in Python?

Use configparser from the standard library: config.read("file.ini", encoding="utf-8"), then config["section"]["key"] or getint() and getboolean() for typed values.

Should I use INI or TOML for a new project?

TOML, if you have the choice: it looks similar but has a specification and real types, so every parser reads it the same way. Use INI where a program requires it.

Related formats

Other file types explained

Keep going

Related

Sources

Last reviewed 27 October 2026.