TOML Merge
Combine a base TOML document with an overlay. Deeply merges nested tables; arrays and conflicts resolve by the policy you pick.
What this tool does
It parses two TOML documents — A (the base) and B (the overlay) — into value trees, deep-merges them into one tree, and re-emits that tree as a single TOML document. The merge is recursive: where both sides hold a table at the same key, the tables combine key by key rather than one replacing the other wholesale. Keys that exist only in A or only in B both survive.
The intent it closes: "I have two TOML files and I need them combined into one, with a rule for what happens when they disagree." The usual sources are a shared base.toml plus a per-environment prod.toml, a checked-in Cargo.toml plus a generated fragment, or a default config plus a user's overrides.
Two choices steer the result: the Conflict policy decides who wins when A and B hold different scalar values at the same key, and the Arrays mode decides what happens when both sides hold an array there. Everything else just combines.
When you'd reach for it
- Layer an environment overlay onto a base config. Paste
base.tomlas A andprod.tomlas B, leave Conflict on Overlay wins (B) — B's values override A's, new sections from B get appended. - Apply user overrides to frozen defaults. Put the defaults in A, the user file in B, Conflict Overlay wins (B). The user's values take precedence wherever they collide.
- Protect a base from accidental overwrites. Switch Conflict to Base wins (A) so B can only add keys, never change existing ones — "defaults plus optional additions."
- Sanity-check that two configs agree. Set Conflict to Error on conflict; the merge refuses and reports the moment A and B hold different values at the same key.
- Accumulate a list across files. Set Arrays to Concat (A + B) or Union (dedup) to grow an array instead of letting one side replace it.
- Combine files holding secrets. Both documents parse and merge in your tab, so a config full of API keys never leaves the machine.
How the merge works
One click of Merge (it also auto-runs as you type) takes both inputs through three stages.
1. Parse A and B
Each input is parsed against the TOML 1.0 grammar into a value tree. If either input is blank the merge stops before parsing — A empty reports Left input (A) is empty. and B empty reports Right input (B) is empty. If either document is malformed, the parser error surfaces in the status bar and no output is written.
2. Walk the two trees together
Starting at the root, the merger compares A and B key by key. Where both sides hold a table, it recurses into that table and merges its keys. Where a key exists on only one side, that value is carried straight through. The real decisions happen only at the leaves, where both sides hold a value at the same key.
At a leaf collision the type matters. If both values are arrays, the Arrays mode applies (replace, concat, or union). If they are scalars — or one is an array and the other isn't — the Conflict policy applies: base-wins keeps A, error refuses, and the default carries B through.
3. Emit one TOML document
The merged tree is written out by the same canonical emitter the formatter uses, honouring the Sort option. With Preserve the order is A's keys first, then any keys B introduced. Nested tables become [section] headers, lists of tables become [[name]] blocks, and a blank line separates sections.
Options reference
Conflict
What to do when A and B hold different values at the same key. Overlay wins (B) (default) takes B's value — the layered-config behaviour, "the overlay overrides the base." Base wins (A) keeps A's value, so B can only add keys it doesn't already share with A. Error on conflict refuses to guess: when the two values differ it throws Conflict at value: pick a conflict policy. and produces no output. Identical values on both sides never trip the error — only a genuine disagreement does.
Arrays
What to do when both sides hold an array at the same key. Replace (default) discards A's array and keeps B's — TOML's natural assignment behaviour. Concat (A + B) appends B's items after A's, keeping every entry including duplicates. Union (dedup) concatenates then removes duplicates by deep value equality, so an item that appears in both A and B survives once. This mode only fires when both sides are arrays; if one side is an array and the other is a scalar, that's a type collision and the Conflict policy decides instead.
Sort
Key order in the emitted document. Preserve writes A's keys in their original order followed by any new keys from B. Alphabetical sorts every table's keys A–Z. Alpha, tables last sorts A–Z but pushes table-valued and array-of-table keys after the scalars, so host and port sit above [database] in each section.
Output
- One merged TOML document. A single tree, written as canonical TOML 1.0.
- Encoding: UTF-8, no BOM. Line endings are LF (
\n). - Layout: one space on each side of
=, a blank line before each section header, lists of tables emitted as repeated[[name]]blocks. - Comments are not preserved. The merge runs on parsed values, not on the source text, so any
#comments in A or B are gone from the output. - Download:
merged.toml, content typetext/plain;charset=utf-8.
Example
Input A (base):
[server]
host = "0.0.0.0"
port = 8080
workers = 4
tags = ["prod"]
[database]
url = "postgres://localhost/app"
pool = 10
Input B (overlay):
[server]
port = 9090
tags = ["api"]
[database]
pool = 25
[cache]
backend = "redis"
Output (defaults — Overlay wins, Arrays replace, Preserve order):
[server]
host = "0.0.0.0"
port = 9090
workers = 4
tags = ["api"]
[database]
url = "postgres://localhost/app"
pool = 25
[cache]
backend = "redis"
host and workers come only from A, so they survive. port and pool collide, and B wins. tags is an array on both sides, and under Replace B's ["api"] takes over — switch Arrays to Concat for ["prod", "api"]. The whole [cache] section is new in B, so it's appended at the end.
Recipes by intent
Apply a production overlay to a base config
A = base.toml, B = prod.toml. Conflict Overlay wins (B), Arrays Replace. Every value B sets overrides the base; new sections in B are appended; anything B leaves untouched keeps its base value.
Let users override defaults, but only where they actually set something
A = the defaults, B = the user file. Conflict Overlay wins (B). Because keys B never mentions are carried straight from A, the user file only needs to list what it wants to change.
Add to a base without ever overwriting it
Conflict Base wins (A). B can introduce brand-new keys and sections, but any key it shares with A keeps A's value. Useful when A is a frozen, reviewed baseline.
Confirm two configs don't disagree
Conflict Error on conflict. The merge succeeds only if A and B agree everywhere they overlap; the first differing value stops it with Conflict at value: pick a conflict policy. Treat a clean merge as proof the two files are compatible.
Grow a shared list across files
Arrays Concat (A + B) to append B's entries to A's, or Union (dedup) to append and then drop exact duplicates. Both only affect keys where A and B each hold an array.
Layer three or more files
The tool takes two inputs. Merge A and B, copy the result, paste it back as the new A, and merge it with C. The Swap A↔B button helps when you need to flip which side is the base partway through.
Limits and performance
- In-memory, two trees at once. Both inputs, both parsed trees, the merged tree, and the output all sit in memory together. Config-sized files merge instantly; multi-megabyte documents take a moment.
- Comments are not preserved. Parse → merge → emit drops every
#comment from both A and B. If comments must survive, do the merge with a text-level tool instead. - Union dedups by deep equality. Two array items count as duplicates only if their full structure matches; near-identical tables that differ in one field both stay.
- The textarea is the slow part on huge output. Past tens of megabytes, painting the merged result into the output pane lags after the merge finishes — use Download .toml rather than Copy.
Errors and how to fix them
Left input (A) is empty. / Right input (B) is empty.
The merge needs both documents. Paste base TOML into the left pane and overlay TOML into the right, or use Open file (A) / Open file (B) to load them. Try sample fills both with a worked example.
Conflict at value: pick a conflict policy.
Conflict is set to Error on conflict and A and B hold different values at the same key. Either reconcile the two files so they agree, or switch Conflict to Overlay wins (B) or Base wins (A) to choose a winner automatically.
A parse error / the status bar goes red
One of the two inputs isn't valid TOML 1.0 — an unclosed quote, an array that never closes, a missing =, or a duplicate key in the same table. Run each file through the TOML Validator to find the exact line, then merge the fixed versions.
An array I expected to grow got replaced instead
Arrays defaults to Replace, which keeps only B's array. Switch to Concat (A + B) to append, or Union (dedup) to append without duplicates. Note this only applies where both sides hold an array at that key.
My # comments disappeared
Expected. The merge works on parsed values, so the emitter has no comments to write. Keep your source files as the record of intent and treat the merged output as a generated artifact.
FAQ
How are arrays of tables ([[…]]) merged?
As arrays, under the same Arrays mode as scalar arrays. Replace gives you B's blocks; Concat appends B's blocks after A's; Union appends then drops blocks that are structurally identical. The merge does not match [[…]] entries by an id field — it treats the array as a whole.
What happens when one side has a table and the other a scalar at the same key?
That's a type collision, not a deep merge — there's no sensible way to fold a value into a table. The Conflict policy decides: Overlay wins takes B, Base wins keeps A, Error on conflict stops.
Does it preserve key order?
With Sort on Preserve, yes — A's keys first in their original order, then any keys B added. Pick Alphabetical or Alpha, tables last if you'd rather have a sorted, diff-stable result.
Is the merge symmetric — does swapping A and B give the same result?
No. The conflict winner and array order both depend on which file is A and which is B, so swapping them changes the output. Use the Swap A↔B button to try it both ways.
Is my data uploaded?
Never. Both documents are parsed and merged entirely in your browser. You can go offline after the page loads and the merge still works — safe for configs holding secrets or credentials.