TOML to INI Converter
Backport a TOML document to a plain INI file for systems that still expect it. Tables become [section] blocks; nested tables become dotted [a.b] sections; arrays are joined with commas.
What this tool does
It parses a TOML 1.0 document into a value tree, then walks that tree once to emit a classic INI file. Root-level scalars are written first, before any header. Every TOML table becomes an INI [section]; a nested table becomes a dotted [parent.child] section, because INI has no nesting and dotted names are the only way to encode depth. Datetimes come out as ISO 8601 strings, and arrays are flattened by whichever array policy you pick.
The intent it closes: "My config lives in TOML, but one stubborn consumer only reads INI." That consumer is usually a Python configparser script, an old Windows desktop app reading win.ini-style files, a sysadmin tool, or a Node ini / .NET reader buried in a legacy service you can't rewrite this quarter.
One trade-off to know going in: INI is a lossy target. It has no nested objects, no real array type, and no comments survive the parse. Keep your TOML as the source of truth and treat the INI as a generated build artifact.
When you'd reach for it
- Feed a Python
configparserconsumer. Leave Dialect on Python so keys come out askey = valuewith spaces — the formconfigparserwrites natively. - Generate a Windows-style
.ini. Switch Dialect to Windows for tightkey=valuepairs with no spaces, matching the classic Win32 profile-file look. - Keep a multi-list array recoverable. Set Arrays to JSON-encoded so an array survives as a parseable JSON string instead of an ambiguous comma blob.
- Match a reader that expects repeated keys. Choose Repeat key per item for tools that read
plugin = a/plugin = bas a list. - Flatten a deep config for a flat reader. Nested tables collapse to dotted
[a.b.c]sections automatically — no option needed. - Ship config alongside a TOML source of truth. Author in TOML, emit the
.inias a step in your build and never hand-edit the output.
How the conversion works
One click of Convert runs three stages.
1. Parse the TOML
The full TOML 1.0 grammar is parsed with smol-toml into a value tree — dotted keys collapse into nested tables, integers keep their type, datetimes become date-shaped values. A parse failure shows in the status bar and produces no output, so a clean conversion is also a syntactically valid TOML file.
2. Split roots from sections
The top level of the tree is split in one pass: any key whose value is a plain table goes to the section list; everything else (scalars, arrays, dates) is a root key. Root keys are emitted first, with no header above them, because INI puts loose key = value pairs before the first [section].
3. Emit sections, recursing for depth
Each table is written as [name], then its keys follow. When a key's value is itself a table, the emitter writes a blank line and recurses with a dotted name — [database.pool] for a pool table inside database. Sections are separated by a blank line. There is no depth limit; a three-level nest just yields a longer dotted header. Per-value formatting (quoting, array joining, ISO dates) happens as each line is written.
Options reference
Dialect
Python (key = val) writes a space on each side of the equals sign — host = localhost — which is how Python's configparser emits and the most widely accepted form. Windows (key=val) writes no spaces — host=localhost — matching classic Win32 profile files and a few parsers that are picky about leading whitespace in values. The choice only changes the separator around =; it never changes section headers or quoting.
Arrays
INI has no array type, so this option decides how a TOML array is flattened. Comma-joined (the default) writes the elements as one value joined by commas with no spaces — ports = 8000,8001,8002. It is compact but lossy: a string element that itself contains a comma can no longer be told apart from a delimiter on the way back. Repeat key per item writes the key once per element — port = 8000 on three lines — which readers that accept duplicate keys treat as a list. JSON-encoded writes the whole array as a JSON string — ports = [8000,8001,8002] — the only policy that round-trips an array losslessly, since the receiving side can JSON.parse it.
Output and mapping rules
- Root scalars first. Top-level keys that aren't tables are written before any
[section]header. - Tables →
[section]. Each TOML table becomes one INI section header. - Nested tables → dotted sections. A table inside a table becomes
[parent.child]; deeper nesting just extends the dotted name. This is how depth is encoded, since INI itself is flat. - Arrays follow the Arrays policy — comma-joined, repeated keys, or a JSON string.
- Datetimes → ISO 8601 strings. A TOML datetime is written as its ISO string.
- Quoting. A string value is wrapped in double quotes only when it contains whitespace, a quote, or one of the INI-special characters
= , ; #; embedded double quotes are backslash-escaped. Plain values stay unquoted. - Spacing. A blank line separates sections and precedes each nested subsection.
- Encoding / download. UTF-8 text, downloaded as
output.ini.
Example
Input (TOML):
app_name = "MyApp"
debug = false
[database]
host = "localhost"
port = 5432
[database.pool]
size = 10
timeout = 30
Output (INI, defaults — Python dialect, Comma-joined arrays):
app_name = MyApp
debug = false
[database]
host = localhost
port = 5432
[database.pool]
size = 10
timeout = 30
Note that app_name and debug sit at the top with no header above them, that [database.pool] carries the nesting in its dotted name rather than any indentation, and that the strings drop their quotes because none of them contains a special character.
Recipes by intent
Emit a file Python's configparser will read
Keep Dialect on Python and Arrays on Comma-joined. You get spaced key = value pairs and dotted section names, both of which configparser accepts. Read multi-value entries back with a .split(",") on your side.
Produce a classic Windows .ini
Switch Dialect to Windows. Pairs come out as key=value with no surrounding spaces, the convention old Win32 profile readers expect.
Keep arrays recoverable on the other side
Set Arrays to JSON-encoded. Each array is written as a JSON literal string, so the consumer can JSON.parse it back into a real list — the only lossless array option here.
Target a reader that wants list-style duplicate keys
Choose Repeat key per item. An array of three becomes the same key on three lines, which list-aware INI readers fold back into an array.
Limits and performance
- In-memory. The input text, the parsed tree, and the INI output all coexist in memory. Config-sized files convert instantly; a multi-megabyte TOML can hang the tab for a moment while the output string is assembled. As a rule of thumb these in-browser tools are comfortable to roughly 500 MB on desktop and ~100 MB on mobile.
- The textarea is the slow part on big output. Past tens of megabytes, painting the result into the right-hand pane lags even after the conversion finishes — prefer Download .ini over Copy.
- Comments are not preserved. The parser builds a value tree with no comment nodes, so none reach the INI. Keep the TOML as the source of truth and convert a copy.
- INI is structurally flatter than TOML. Depth survives only as dotted section names, and arrays survive only as joined or JSON-encoded strings. The conversion is one-way clean, not a guaranteed round-trip.
Errors and how to fix them
Status bar goes red on click / a parse error
The input isn't valid TOML 1.0 — usually an unclosed quote, an array that opens with [ but never closes, a missing = between key and value, or a duplicate key in the same table. The conversion only fails at the parse step, so the TOML Validator will point at the exact line and column.
My array-of-tables came out as [object Object]
An array-of-tables ([[servers]]) is an array whose elements are themselves tables, and INI has no shape for that. Under the comma and repeat policies each element stringifies to [object Object]. Switch Arrays to JSON-encoded to preserve the data as a JSON string, or restructure the source into named tables ([servers.a], [servers.b]) before converting.
My # comments are gone
Expected. The parser doesn't carry comments into the value tree, so the emitter has nothing to write. Keep your original TOML if the comments matter; convert a copy.
A value got wrapped in double quotes I didn't ask for
That string contained whitespace, a quote, or one of the INI-special characters = , ; #, so it was quoted to stay unambiguous — embedded double quotes are backslash-escaped. Values without any of those characters are written bare.
My comma-joined list can't be split cleanly
Comma-joining is lossy when an element already contains a comma — the delimiter and the data look identical on the way back. Use JSON-encoded arrays for anything you need to parse reliably downstream.
FAQ
Which INI dialect does it emit?
A conservative subset that Python's configparser, Node's ini, and .NET INI readers all accept: [section] headers, key = value or key=value pairs, and dotted section names. No type tags and no continuation lines. The Dialect option toggles only the spacing around =.
How is the nesting in my TOML preserved?
Purely through dotted section names. A [database.pool] header tells a reader the keys belong to pool inside database. INI itself is flat, so there is no indentation — the dots carry the hierarchy.
How are TOML datetimes written?
As ISO 8601 strings. INI has no date type, so the ISO string is the lossless choice; parse it back on your side if you need a real date object.
Is my data uploaded?
Never. Parsing and emission run entirely in your browser on top of smol-toml. You can go offline before clicking Convert and it still works — safe for configs that hold secrets or connection strings.
Can I get my TOML back from the INI later?
Not guaranteed. INI drops comments, flattens structure into dotted names, and (under the comma policy) blurs arrays, so a perfect round-trip isn't promised. If you do need to read an INI back into TOML, that's a separate tool — see INI → TOML below.