# TOML Format & Validate

> Validate, lint and format TOML 1.0 with exact error locations; explore it as a tree; convert to and from JSON.

Category: Text & Data · Page: https://util.caruana.me/tools/toml/

## API, MCP and A2A

### Format, validate and lint TOML (`toml_format`)

`GET|POST https://util.caruana.me/api/toml/format` · MCP tool `toml_format` · A2A skill `toml_format` · [reference](https://util.caruana.me/reference/toml_format/)

Parses `input` as TOML 1.0.0 and returns every finding as a diagnostic with a rule ID, severity, line, column and offset: syntax errors, duplicate keys and tables, integers beyond 64 bits (errors); TOML 1.1-only syntax, inconsistent indentation, trailing spaces, mixed line endings (warnings); unnecessary key quotes, split or out-of-order tables, long inline tables, empty tables, long lines (info). Only the first syntax error is reported. An invalid document is a successful response with `valid: false`, so check `valid`. For a valid document it also returns the canonical formatting and the data as JSON. Formatting keeps every comment and never changes the data: keys are written `key = value` with one space around `=`, indentation and trailing spaces are removed, there is one blank line before each table header, arrays longer than `max_line_length` are broken one element per line, and the text ends with one line break. Strings, numbers and dates are copied as written. In `value`, integers beyond ±(2^53 − 1) are decimal strings, `inf`/`nan` are the strings `"inf"`, `"-inf"` and `"nan"`, and dates and times are ISO 8601 strings.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `input` | string (≤ 50000 chars) | yes | The TOML text. Send it as a string field or as a raw `text/plain` POST body. |
| `indent` | integer (1–8) | no | Spaces per level for arrays that are broken over several lines. |
| `max_line_length` | integer (0–1000) | no | Arrays longer than this are broken one element per line, and longer lines are reported (`line-length`). `0` turns both off. |
| `sort_keys` | boolean | no | Sort keys within each table, by UTF-16 code units. Tables keep their order (the order of `[[array]]` tables is data). |

Privacy: The server receives the TOML text, processes it in memory inside the request, and returns the result. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. The server makes no network requests. For confidential documents use the page, which does the same work in your browser and sends nothing.

### Convert TOML to JSON (`toml_to_json`)

`GET|POST https://util.caruana.me/api/convert/toml-to-json` · MCP tool `toml_to_json` · A2A skill `toml_to_json` · [reference](https://util.caruana.me/reference/toml_to_json/)

Parses `input` as TOML 1.0.0 and returns the same data as JSON text and as a JSON value. Tables become objects and keep their order. Integers within ±(2^53 − 1) are numbers; larger ones are decimal strings so no digit is lost. `inf`, `-inf` and `nan` become the strings `"inf"`, `"-inf"` and `"nan"`. An offset date-time becomes an ISO 8601 string with its offset (`1979-05-27T07:32:00.000-08:00`); a local date-time, date or time becomes its TOML text (`1979-05-27T07:32:00.000`, `1979-05-27`, `07:32:00.000`). Fractional seconds are written to the millisecond. Comments are dropped: JSON has none.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `input` | string (≤ 200000 chars) | yes | The TOML text, as a string field or a raw `text/plain` POST body. |
| `indent` | integer (0–8) | no | Spaces per JSON indentation level; `0` gives minified JSON. |

Privacy: The server receives the TOML text, processes it in memory inside the request, and returns the result. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. The server makes no network requests. For confidential documents use the page, which does the same work in your browser and sends nothing.

### Convert JSON to TOML (`json_to_toml`)

`GET|POST https://util.caruana.me/api/convert/json-to-toml` · MCP tool `json_to_toml` · A2A skill `json_to_toml` · [reference](https://util.caruana.me/reference/json_to_toml/)

Parses `input` strictly as JSON and writes it as TOML 1.0.0. The top level must be an object. Strings, booleans and numbers become key = value pairs (numbers are copied as written, so `1.0` stays a float); non-empty objects become [table] sections and arrays of objects become [[array]] tables; objects inside other arrays become inline tables; an empty object is `{}`. Within each table, plain keys come first, then sub-tables. Keys that are not bare (A–Z, a–z, 0–9, _ and -) are quoted. TOML has no null, so a null anywhere is an error that names its JSON Pointer; so is an integer beyond 64 bits. The result is formatted like `/api/toml/format` output.

| Parameter | Type | Required | Description |
| --- | --- | --- | --- |
| `input` | string (≤ 200000 chars) | yes | The JSON text (an object), as a string field or a raw `text/plain` POST body. |
| `indent` | integer (1–8) | no | Spaces per level for arrays that are broken over several lines. |
| `max_line_length` | integer (0–1000) | no | Arrays longer than this are broken one element per line, and longer lines are reported (`line-length`). `0` turns both off. |
| `sort_keys` | boolean | no | Sort keys in every table and inline table, by UTF-16 code units. Array order never changes. |

Privacy: The server receives the JSON text, processes it in memory inside the request, and returns the result. Your input is not stored or logged; only the per-call metadata row (operation, time, status, sizes) is recorded. The server makes no network requests. For confidential documents use the page, which does the same work in your browser and sends nothing.


## User guide

The studio works like a desktop window: a menu bar (**File**, **Edit**, **View**, **Settings**, **Help**) with the **full-screen** icon at the right of the same row. **Settings → Options…** opens the options in an overlay (or **View → Inline config** shows them in the card), and **Help → Keyboard shortcuts…** lists the keys.

1. **Paste or type TOML** in the **Input** view, or choose **File → Load sample** to start from an example. The editor numbers its lines, and the document is checked as you type.
2. Read the verdict. The **Problems** view (the button at the right of the status line, which shows the count) lists every finding with its line, column, rule and a short explanation. Errors make the document invalid; warnings and notes are about style. Click a finding to jump to that spot in the input.
3. Choose **Edit → Format** (or press **Ctrl+Enter**). The **Formatted** view shows the canonical layout; **Use as input** copies it back into the editor so you can keep working on it.
4. Open the **Tree** view to browse the data as it will be read (see the value mapping below). Every table and array can be collapsed, and each entry shows its type and its path.
5. **Copy** or **Download** the result, or **File → Clear input** to start again. The **full-screen** icon at the right of the menu bar makes the studio fill the window; **Esc** returns.

**Settings → Options…** sets how the output looks: the **indent** for arrays broken over several lines, the **line length** after which a long array is broken (and longer lines are reported), and **sort keys** to order the keys in each table.

**Formatting keeps your comments.** Most TOML tools re-serialise the parsed data and lose every comment. This one rewrites the layout around them: comments on their own line, at the end of a line, and inside multi-line arrays all stay where they were, attached to the same key or element. Strings, numbers and dates are copied exactly as written, so `1_000`, `0xDEAD_BEEF`, `1.0` and a literal `'C:\path'` survive unchanged.

**What the checks catch.** Beyond outright syntax errors, the usual TOML surprises are a key or `[table]` defined twice (often a `[table]` header that collides with a dotted key such as `a.b = 1`), an integer too large for 64 bits, and syntax from the TOML 1.1 draft that some parsers accept and others reject: line breaks or a trailing comma inside `{ inline tables }`, the `\e` and `\xHH` escapes, and times without seconds (`07:32`). Mistakes from other formats get a hint: `True` (booleans are lowercase), `null` (TOML has none) and unquoted strings.

**Converting.** The same data can be turned into JSON, or JSON into TOML, through the API (see below). JSON has no comments, dates or 64-bit integers, so the conversion follows the value mapping in the specification.

## Specification

**Grammar.** [TOML 1.0.0](https://toml.io/en/v1.0.0). The parser is smol-toml, which also accepts some TOML 1.1 syntax; that syntax is reported as a warning (rule `toml-1-1-syntax`) so the document stays readable by TOML 1.0 parsers. A UTF-8 byte order mark is accepted with a warning. Only the first syntax error is reported, because after a syntax error the rest of the text has no reliable structure.

**Positions.** `line` and `column` are 1-based, and columns count Unicode code points, so an emoji counts as one character. `offset` is 0-based in UTF-16 code units (what a browser text box uses), so the editor can select the spot exactly. LF, CRLF and a lone CR each end a line.

**Rules.** Severity `error` makes the document invalid; `warning` and `info` do not. "Format fixes" means the formatted output no longer has the problem.

| Rule                       | Severity | What it checks                                                                                                                                                                                                                                                              | Format fixes                                                                                                       |
| -------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `syntax`                   | error    | The text is TOML. The message names the problem and, for common mistakes, how to fix it.                                                                                                                                                                                    | No                                                                                                                 |
| `duplicate-keys`           | error    | Each key and table is defined once: no repeated key in a table or inline table, no repeated `[table]` header, no `[table]` that reopens a table made by dotted keys, an inline table, a value or an `[[array]]`, and no dotted key that extends a table defined elsewhere.  | No                                                                                                                 |
| `integer-range`            | error    | Integers fit in 64 bits (−9,223,372,036,854,775,808 to 9,223,372,036,854,775,807), as TOML 1.0 requires. smol-toml would otherwise accept larger ones.                                                                                                                      | No                                                                                                                 |
| `toml-1-1-syntax`          | warning  | No TOML 1.1-only syntax: line breaks, comments or a trailing comma inside an inline table; `\e` and `\xHH` escapes; times and date-times without seconds.                                                                                                                   | Yes, except comments inside an inline table, which stay (one entry per line) because removing them would lose them |
| `inconsistent-indentation` | warning  | Keys under the same table header (or at the top) all have the same indentation. Consistent indentation is not reported, but Format removes it.                                                                                                                              | Yes                                                                                                                |
| `trailing-spaces`          | warning  | No spaces or tabs at the end of a line. Spaces inside a multi-line string are part of its value and are not reported.                                                                                                                                                       | Yes                                                                                                                |
| `final-newline`            | warning  | The text ends with exactly one line break.                                                                                                                                                                                                                                  | Yes                                                                                                                |
| `line-endings`             | warning  | All line breaks use the same style.                                                                                                                                                                                                                                         | Yes, unless a multi-line string contains the other style (its value is kept)                                       |
| `byte-order-mark`          | warning  | No U+FEFF at the start.                                                                                                                                                                                                                                                     | Yes                                                                                                                |
| `unnecessary-quotes`       | info     | A quoted key (or part of a dotted key) that could be bare: only A–Z, a–z, 0–9, `_` and `-`.                                                                                                                                                                                 | Yes                                                                                                                |
| `table-order`              | info     | A table is not defined after its own sub-tables (`[a.b]` then `[a]`), and the sub-tables of one table are not split up by unrelated tables (`[a.x]`, `[b]`, `[a.y]`). Both are legal TOML but hard to read. Sub-tables of each `[[array]]` element are tracked per element. | No (tables are never reordered)                                                                                    |
| `long-inline-table`        | info     | An inline table is at most 80 characters when written on one line; a `[table]` section reads better. Only the outermost of nested inline tables is reported.                                                                                                                | No                                                                                                                 |
| `empty-table`              | info     | A `[table]` or `[[array]]` header has keys or sub-tables under it. `x = {}` is not reported.                                                                                                                                                                                | No                                                                                                                 |
| `multiline-string-crlf`    | info     | A multi-line string contains CRLF line breaks. They are part of the value here, but TOML lets parsers normalise them to LF, so other tools may read a different string.                                                                                                     | No                                                                                                                 |
| `line-length`              | info     | Lines are at most the configured length (default 120 characters; 0 or none turns the check off).                                                                                                                                                                            | No                                                                                                                 |

**Formatting.**

- One `key = value` per line with one space either side of `=`, no indentation, and no trailing spaces. A comment at the end of a line follows one space.
- Dotted keys are written without spaces around the dots; quotes that are not needed are removed.
- Blank lines: none at the start or end, at most one in a row, none directly after a table header, and exactly one before each header. Comments directly above a header (no blank line between) stay with the header.
- Arrays stay on one line unless they were already written over several lines, contain comments, or would make the line longer than the line length; then each element goes on its own line, indented by the indent setting per level, with a trailing comma. Blank lines between elements are kept (at most one).
- Inline tables are written `{ a = 1, b = 2 }` on one line (an empty one as `{}`).
- TOML 1.1 syntax is rewritten to TOML 1.0: `\e` becomes `\u001B`, `\xHH` becomes `\u00HH`, `07:32` becomes `07:32:00`.
- The output ends with one line break. A document that uses only CRLF stays CRLF; anything else becomes LF. A byte order mark is removed.
- **Sort keys** orders the keys of each table by UTF-16 code units, within each group of lines separated by blank lines; a comment directly above a key moves with it, and comments at the very top of the document stay there. Keys inside inline tables are sorted too. Tables themselves are never reordered: the order of `[[array]]` elements is data, and sub-tables belong to the element above them.
- Strings (including multi-line strings, byte for byte), numbers and dates are copied as written.
- The formatted document always parses to the same data as the input. This is checked on every format: if it ever did not, the input would be returned unchanged.

**Value mapping to JSON** (the Tree, `value` and TOML → JSON).

| TOML                        | JSON                                                                                                                          |
| --------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| String, boolean             | The same.                                                                                                                     |
| Table, inline table         | Object, keys in document order.                                                                                               |
| Array, array of tables      | Array.                                                                                                                        |
| Integer within ±(2^53 − 1)  | Number (`0xff` → `255`).                                                                                                      |
| Integer beyond ±(2^53 − 1)  | Decimal string, every digit kept: `9223372036854775807` → `"9223372036854775807"`.                                            |
| Float                       | Number (`1.0` → `1`, `-0.0` → `0`); `inf`, `-inf`, `nan` → the strings `"inf"`, `"-inf"`, `"nan"`.                            |
| Offset date-time            | ISO 8601 string with the offset as written and milliseconds: `1979-05-27 07:32:00-08:00` → `"1979-05-27T07:32:00.000-08:00"`. |
| Local date-time, date, time | The TOML text, with `T` and milliseconds: `"1979-05-27T07:32:00.000"`, `"1979-05-27"`, `"07:32:00.000"`.                      |

Fractions of a second beyond milliseconds are truncated (`.999999` → `.999`). A key named `__proto__` is an ordinary key.

**JSON → TOML.** The top level must be an object. Strings, booleans and numbers become `key = value`; numbers are copied as written, so `1.0` stays a float. Non-empty objects become `[table]` sections and non-empty arrays whose elements are all objects become `[[array]]` tables; objects inside other arrays become inline tables, and an empty object is `{}`. In each table the plain keys come first, then the sub-tables, so the key order can change. A `null` is an error naming its JSON Pointer (TOML has no null); so are an integer beyond 64 bits and a string with an unpaired surrogate. Duplicate JSON keys keep the last value. The output is formatted as above.

**Statistics.** `tables` counts tables below the root ([table] sections, dotted-key tables, inline tables and each `[[array]]` element); `keys` counts keys in all tables; `arrays` counts arrays, including arrays of tables; `depth` is 1 for a flat document and one more per level of table or array.

**Not checked.** Whether keys or values make sense for any particular program (Cargo, pyproject, Hugo and so on: use a schema for that); the order of keys; naming style (`snake_case` versus `kebab-case`); and whether a float or date can be represented exactly by the program that reads it.

**API.**

| Endpoint                         | Input limit        | Parameters                                                                                                                       |
| -------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `POST /api/toml/format`          | 50,000 characters  | `input`, `indent` (1–8, default 2), `max_line_length` (0–1000, default 120; 0 turns wrapping and `line-length` off), `sort_keys` |
| `POST /api/convert/toml-to-json` | 200,000 characters | `input`, `indent` (0–8 spaces, default 2; 0 is minified)                                                                         |
| `POST /api/convert/json-to-toml` | 200,000 characters | `input`, `indent`, `max_line_length`, `sort_keys`                                                                                |

`input` can be a string field or a raw `text/plain` body. `/api/toml/format` returns `valid`, `summary` (counts of errors, warnings and infos), `diagnostics`, `formatted`, `value` and `stats`. An invalid document is a 200 response with `valid: false` and `formatted` and `value` set to null, not an HTTP error. The conversions return 422 `unprocessable_input` with the line and column (or JSON Pointer) when the input cannot be converted. The page has no size limit.
