Migrating to TOON v4: Breaking Changes and Upgrade Checklist
Upgrading from TOON v2/v3 to v4? Key folding is gone, # lines are now comments, and decoders must upgrade first. A step-by-step checklist with fixes.
Upgrading @toon-format/toon from 2.x to 4.x is mostly painless, but three things can bite. Key folding is gone. Lines that start with # are now comments. And v4 encoders emit headers that older decoders cannot read. Upgrade decoders first, scan stored documents, drop folding options, then upgrade encoders.
What breaks when you upgrade to TOON v4?
The npm package jumped from 2.3.1 to 4.0.0 on July 22, 2026, to match spec 4.0. There was never a 3.x, so "v3" below means spec 3.x as implemented by library 2.x (releases). The current release is 4.1.1. If you want the feature tour first, read what's new in TOON v4. This post covers only what can break, based on the spec changelog and our tests of 2.3.1 against 4.1.1.
| Change | Who is affected | Fix |
|---|---|---|
| Keyed tables and nested field groups | Anyone decoding v4 output with a 2.x decoder | Upgrade decoders before encoders |
Full-line # comments | Stored v3 docs with #-leading cells, keys or root scalars | Scan for ^ *#, then re-encode |
| Key folding and path expansion removed | Code using keyFolding, flattenDepth or expandPaths | Re-hydrate with a 2.x decoder, re-encode |
| Depth jumps are strict errors | Hand-written TOON that skips a level | Re-indent, or decode non-strict and re-encode |
| Strict number grammar | Hand-written values like .5 or 1_000 | Write 0.5 and 1000 |
Step 1: Why upgrade decoders before encoders?
A v4 encoder turns a map of uniform objects into a keyed table. That is a new header shape. Here is real 4.1.1 output:
flags[2:]{enabled,rollout}:
darkMode: true,50
newCheckout: false,0We fed that to the 2.3.1 decoder. In strict mode (the default) it throws Invalid array length: "2:". In non-strict mode it returns {"flags[2":"]{enabled,rollout}:"}: no error, wrong data. A nested field group header such as orders[2]{id,customer{name,country},total}: fails the same way. This is why the changelog says strict v3 decoders "fail closed; non-strict ones mis-decode it silently, so upgrade decoders before encoders."
So the first step is to list every place that reads TOON: services, queues, caches, test fixtures and ports in other languages. Upgrade those to a v4-capable decoder first. If TOON only goes into LLM prompts and is never decoded by code, the risk sits on the model side, not the parser side. Check that your prompts still work with the new forms.
Step 2: How do you find documents affected by # comments?
This is the one change that alters how existing v3 output is read. The changelog calls it "the only v4 change to the decoded meaning of conforming v3 output." A v3 encoder did not quote strings starting with #, so a Slack-style channel list came out like this (library 2.3.1):
channels[2]{name,members}:
#general,120
#random,85Under 4.1.1 both rows are now comment lines. Strict decoding fails with Expected 2 tabular rows, but got 0. Non-strict decoding returns {"channels":[]}, which silently loses the data. A root-level scalar like #hashtag decodes to an empty object. Inline arrays are safe: in tags[2]: #urgent,billing the # is not the first character of the line, so it stays data.
Find affected files with a search for the pattern the changelog gives, /^ *#/:
grep -rnE '^ *#' --include='*.toon' ./dataThe fix is to decode those files with the old library and re-encode with v4. The new encoder quotes the values:
channels[2]{name,members}:
"#general",120
"#random",85We checked that the 2.3.1 decoder reads this quoted form correctly as well, so re-encoded files work with both old and new readers during a rolling upgrade.
Step 3: How do you replace keyFolding and expandPaths?
Spec 4.0 removed the keyFolding and flattenDepth encoder options and the expandPaths decoder option. With folding enabled, 2.3.1 produced:
server.http:
port: 8080
host: 0.0.0.0
db.pool.max: 20A v4 decoder treats dotted keys as literal names. It returns {"server.http":{...},"db.pool.max":20}, not nested objects. On the encoding side, 4.1.1 does not throw if you still pass keyFolding: "safe". It ignores the option and writes nested output. Your code will look fine, but the output shape will have changed.
To recover stored folded data, follow the changelog: decode once with a v3 decoder using expandPaths: "safe", then re-encode. An npm alias lets both versions live side by side during the migration:
npm install @toon-format/toon@4 toon-v3@npm:@toon-format/toon@2.3.1
// rehydrate.mjs
import { decode as decodeV3 } from "toon-v3";
import { encode } from "@toon-format/toon";
const data = decodeV3(oldText, { expandPaths: "safe" });
const newText = encode(data); // v4 output, nested objects restoredFor the folded example above, the re-encoded output is:
server:
http:
port: 8080
host: 0.0.0.0
db:
pool:
max: 20Step 4: What about hand-written TOON files?
Prompt templates, fixtures and few-shot examples written by people need a separate check, because no encoder guarantees them.
- Indentation jumps.
a:followed by a line indented four spaces now fails strict decoding withIndentation depth jump: expected depth 1, but found 2. Re-indent, or decode once withstrict: falseand re-encode. - Trailing comments. Comments are full-line only.
timeout: 5000 # millisecondsdecodes to the string"5000 # milliseconds"in 4.1.1. Move the comment onto its own line above the field. - Loose numbers. Under the new grammar,
.5,+5,0x10,1_000andInfinitydecode as strings. Write canonical decimals.
Our TOON data validation guide shows how to add a decode-and-compare check in CI so these problems fail the build, not production.
Step 5: Which encoder options changed?
Spec 3.3 renamed indent to indentSize, and spec 4.1 uses the new name throughout. Library 4.1.0 added "Accept the indentSize option named by the spec" (v4.1.0 notes). In our tests, 4.1.1 still honors the old indent name, and the spec allows keeping it as a deprecated alias. Switch to indentSize anyway, so ports in other languages read the same config. delimiter (comma, tab or pipe) is unchanged.
Step 6: Why did my snapshot tests change?
Even with no options removed, v4 output differs from 2.x output for some inputs. That is intended. Spec 4.1 says tabular form is "mandatory wherever detection succeeds." Maps of uniform objects become keyed tables, arrays with uniform sub-objects become nested field groups, and #-leading strings get quotes. Empty arrays are always key: []. Review these diffs, don't blindly update them. They are usually smaller, and the benchmark measured the gains: keyed feature flags at 54.6% fewer tokens than JSON, and contacts with nested groups at 66.5% fewer (token-efficiency results).
If prompts in your app explain TOON to the model, add one line about the keyed header ([N:] means "rows start with their own key"). For more on writing prompts around the format, see TOON best practices.
The TOON v4 upgrade checklist
- List every component that decodes TOON, including ports in other languages.
- Upgrade those decoders to v4 and deploy them.
- Run
grep -rnE '^ *#'over stored TOON. Decode matches with 2.x and re-encode with v4. - Remove
keyFolding,flattenDepthandexpandPathsfrom your code. Re-hydrate any folded data. - Rename
indenttoindentSize. - Strict-decode every hand-written TOON file in CI to catch depth jumps, trailing comments and loose numbers.
- Upgrade encoders to
@toon-format/toon@4and review snapshot diffs. - Re-measure token counts on real payloads with the JSON to TOON converter.
Frequently Asked Questions
Is upgrading @toon-format/toon from 2.x to 4.x a breaking change?
For most data, no. Documents encoded without key folding decode the same way. Three things break: key folding and path expansion are gone, lines starting with # are now comments, and v4 encoders emit keyed and nested-group headers that v2/v3 decoders cannot read. Hand-written files that skip an indentation level also fail in strict mode.
Why should I upgrade TOON decoders before encoders?
A v4 encoder emits new headers such as flags[2:]{enabled,rollout}: for maps of uniform objects. Strict v3 decoders reject them, and non-strict v3 decoders mis-decode them silently. A v4 decoder reads everything a v3 encoder produced, apart from #-leading lines, so updating readers first is the safe order.
What replaces keyFolding in TOON v4?
Nothing. Key folding and path expansion were removed from the spec, and in @toon-format/toon 4.1.1 a keyFolding option is ignored and output stays nested. To recover data stored with folded keys, decode it once with a 2.x decoder using expandPaths: "safe", then re-encode it with v4.
How do I find stored TOON files affected by the comment change?
Search for lines whose first non-space character is #, for example with grep -rnE '^ *#' on your TOON files. Those lines were data in v3 and are comments in v4. Re-encoding the decoded data with a v4 encoder quotes the strings, and the quoted form decodes identically in both versions.
Recommended Reading
TOON in Python and JavaScript: A Hands-On SDK Guide
Install, encode, decode, and validate TOON in Python and JavaScript/TypeScript with copy-paste examples and a JSON-to-TOON migration checklist.
TOON v4: Keyed Tables, Nested Field Groups and Comments
TOON spec 4.1 adds keyed tables for maps of objects, nested field groups and full-line comments, and drops key folding. What changed and why it matters.
Token-Efficient Apps with TOON and the Vercel AI SDK
How to format context as TOON inside Vercel AI SDK apps to cut input tokens—while keeping generateObject and tool calls on JSON for reliable structured output.