YamlHippo user guide
YamlHippo is a YAML viewer that tells you exactly where it broke. Paste YAML into the Text tab and it is checked as you go; Tree shows it as a tree you can filter; Types gives a reference for every custom tag in it; Schema shows a JSON Schema that describes it; Diff compares two documents side by side.
Valid YAML is also checked for likely mistakes (warnings) — above all for the values another YAML parser would read differently. Everything happens in your browser: nothing you paste or load is uploaded anywhere.
Getting YAML in
- Paste into the Text tab, or type.
- Load from file reads a
.yaml,.ymlor.jsonfile from your computer (a leading byte order mark is dropped). - JSON is YAML: paste JSON and it is read as YAML, unchanged.
- The text area always accepts what you give it, valid or not. You have to see broken YAML to fix it.
YamlHippo checks the text when you paste, when you press Format, and 400 ms after you stop typing. Above 5 MB it stops checking while you type, because the text area itself slows down at that size; the status bar offers Validate now instead.
Smart paste: tabs, indentation, curly quotes
YAML's structure is its whitespace, and whitespace is what a clipboard damages. When you paste over the whole text, YamlHippo repairs:
- Tabs in the indentation, which YAML forbids. Each one becomes one level of spaces, at the step the document already uses (2 or 4).
- Curly quotes from a word processor or a chat:
“value”becomes"value". Single curly quotes are only straightened where they quote a value, so the apostrophe init’sstays as you wrote it. - No-break spaces and invisible characters (zero-width spaces, a stray byte order mark) that a web page leaves behind.
- Inconsistent indentation — siblings at 3 spaces and 2, a block pasted one level off. This one is only rewritten when the text does not parse as it is and the rewritten text does: re-indenting valid YAML could change what it means.
The content of block scalars (| and >) is never touched: a tab or
a curly quote inside a literal block is yours.
A notice says what was fixed (“replaced 4 tabs in the indentation with spaces; straightened 2 curly quotes”) with an Undo that puts back exactly what you pasted. Typing in the text drops Undo, since restoring the original would throw your edits away.
- Fix paste on the toolbar does the same thing on demand.
- Auto-fix paste (the checkbox on the toolbar) turns the automatic behaviour off; the Fix paste button then appears.
Reading an error
When the YAML is invalid the status bar says exactly where and why. Most YAML errors are indentation errors, and YamlHippo says so in those words:
Line 7, col 1: indented 7 spaces but siblings use 6 (in $.spec.containers[0])
This line is indented deeper than its siblings (line 5), so YAML reads it as
the continuation of the value on the line above. Indent it to match its
siblings — or, if it belongs under the line above, give that line a ':' with
nothing after it.
- Line, col — where the problem is. Columns count characters (an emoji counts as two, as it does everywhere in JavaScript). For an indentation error the whole indentation is underlined.
- The message — for indentation, how deep this line is and how deep its siblings are; otherwise what the parser needed at that point and what was there.
- (in $.spec.containers[0]) — the path of the place it happened.
- The second line is a hint: a plain-English guess at the cause.
YamlHippo keeps a stack of the indentation levels that are open. A line that dedents to a level that is not open — between two of them — matches nothing; the error names the nearest level and the others, so you can see which block the line was meant for. A line indented deeper than its siblings, after a line that does not open a block, is read by YAML as more of the value above, and is reported the same way.
In the text, the line is marked in the gutter and highlighted, and the bad text is
underlined. Click the error to put the caret on it — this works the same
in a file of several megabytes. Most errors also have a second link: Go to the
siblings (the block the indentation disagrees with), Go to the opener
(the [ or { never closed), Go to the opening quote (a string
that never ends).
Only the first error is reported. Fix it and the next one, if any, appears.
Valid YAML gets a summary instead — “Valid YAML — 1,284 keys, depth 7, 2.1 MB”, with the number of documents when there are several — and the count of any warnings.
Warnings: values other parsers read differently
An error means “this is not YAML”. A warning means “this is YAML, but look here”. With Warnings ticked in the Text toolbar — it is, until you untick it — YamlHippo looks for the following.
Most of them are type-coercion traps. YamlHippo reads YAML by the
YAML 1.2 rules, where no is a string. But much of the world's YAML is read
by YAML 1.1 parsers (PyYAML, Go's yaml.v2, many config loaders), where
no is false. A value whose meaning depends on the parser is a bug
waiting to happen, so each one is pointed out rather than silently read one way:
| Warning | Example | Why it matters |
|---|---|---|
| 1.1 boolean | country: NO, on: push | The “Norway problem”: yes, no, on, off, y, n are strings in 1.2 and booleans in 1.1 — keys included. Quote it, or write true/false. |
| Base-60 number | port: 22:22, time: 1:30 | 1.1 reads digits separated by colons as base 60: 1:30 is 90. Quote it. |
| 1.1 number | mode: 0755, n: 1_000 | A leading zero is octal in 1.1 (0755 is 493) and decimal in 1.2; digit separators make a number in 1.1 and a string in 1.2. Write 0o755 for octal, or quote it. |
| Version as a number | python: 3.10, version: 1.2 | 3.10 is the number 3.1 — the trailing zero is gone. Flagged for any number whose fraction ends in 0, and any number under a key that names a version. Quote it. |
| Precision loss | 12345678901234567890, 1e400 | A number that changes when read as a 64-bit float, which is what most YAML libraries turn every number into. |
| Duplicate key | id: 1 … id: 2 | The spec forbids it, but most parsers accept it and keep only one value — the last, usually — losing the other without a word. Every repeat is flagged, with where the key was first defined. |
| Duplicate anchor | &x twice | Aliases after the second refer to it, not the first. |
| Run-on value | - a then - b | A line indented deeper than its siblings continues the value above — here the text “a - b”, not a second entry. Valid, and almost never meant. |
| Empty key | "": 1 | Legal, but usually a name that went missing. |
Rows with warnings are marked in the text's gutter (hover the line number to read them) and in the tree. The status bar gives the count and the first one; click the count for the list, and any entry in it to jump there.
Format, Copy as JSON
Format (Ctrl/⌘+Enter) writes the document out tidily, from the tree — so nothing the YAML says is lost:
- indentation becomes 2 or 4 spaces a level (the Indent menu), lists indented under their key;
- comments are kept, each before the entry it preceded or at the end of its line;
- every scalar keeps its source text —
'single'stays single-quoted,0x1Fstays hex,12345678901234567890keeps every digit; a quoted key that needs no quotes loses them ("name":becomesname:); - tags, anchors and aliases stay; block scalars are re-indented line for line and
keep their
|/>and chomping; - a short flow collection of plain values (
[80, 443]) stays on its line; longer or nested ones are written in block style; - a value that ran over several lines is put on one.
Format never touches invalid YAML: it reports the error instead.
Copy as JSON puts the current document on the clipboard as JSON (with the same indent), and says what did not survive the trip. YAML can say things JSON cannot, and each is reported rather than dropped quietly:
- aliases are copied out in full — JSON has no references — and an alias
inside its own anchor becomes
null; - tags are dropped (
!Ref namebecomes"name"); - keys that are not strings become strings (
1:becomes"1"); .inf,-.infand.nanhave no JSON form and becomenull;- comments are dropped; and of a stream of several documents only the current one is copied.
Numbers are written exactly: 0x1F becomes 31, and a big integer keeps
every digit. The tree marks the nodes that will not survive (see below).
The tree
The Tree tab shows the current document as a collapsible tree:
- Click ▸ (or double-click a row) to open or close a mapping or list.
Mappings show
{ }, lists[ ]. - Expand all, Collapse all and Level… (expand to level 1–10) are on the toolbar. Above 5,000 nodes you are asked first.
- Large mappings and lists show their first 10,000 children, then Show more / Show all.
- Select a row to see its path and its type in the bar at the bottom, with Copy path, Copy value (a collection is copied as YAML) and Show in text.
YAML's own features show on the rows:
- An alias (
*base) is a row with an arrow that says what it stands for — “→ &base (object · 2 keys)” — and is never expanded into a copy. Select it and Go to anchor jumps to the node it points at. - An anchor shows as a chip (
&base) on the node it names. - A custom tag shows as a coloured chip (
!Person), and its whole subtree is tinted and bracketed in the same colour — see Custom tags. Standard tags (!!str) get a plain chip. - A node that will not survive conversion to JSON cleanly — an alias, a custom
tag, a key that is not a string,
.infor.nan— carries a dashed JSON ⚠ chip. Hover it, or select the row, to read why.
Keyboard: ↑/↓ move, → opens or steps in, ← closes or steps out, Home/End, and Enter/Space select.
Filtering the tree
Type in the filter box (or press / on the Tree tab) to narrow the tree to what matches. Every match is shown with the path down to it; everything else is hidden, and a mapping that lost children says so (“3 of 40”). Clear the filter and the tree is back exactly as it was.
- Text matches keys and values (choose Keys, Values or both). Aa makes it case-sensitive; .* makes it a regular expression.
- A path starting with
$matches by position:$.spec.containers[*].image,$..name. - A tag starting with
!matches every node with that tag:!Ref,!!str. Clicking a tag chip does the same.
Enter and Shift+Enter step through the matches; Esc clears the filter.
Custom tags and the Types tab
A custom tag — !Person, !Ref, CloudFormation's !Sub —
names a type the document's reader knows and YAML does not. YamlHippo keeps every
tag on its node and does not act on it, but makes them easy to follow:
- Each tag name has its own colour, the same every time you see it, so
every
!Refin a long file can be followed by eye. - A legend above the tree lists the document's tags with how many of each; click one to filter the tree to it.
The Types tab is a reference for every custom tag, worked out from all its
uses in every document of the file. Nothing declares what a !Person has in
it, so YamlHippo reads every !Person and unions them:
- what the tag goes on — a mapping, a list or a single value;
- for mappings, every field: always present, or
optional (in how many of the uses), and the type of its value
— string, integer, number, boolean, mapping, list, or another tag, so
address: !Addresslinks two types together; - inconsistencies, flagged with ⚠: a field that is a number in some uses
and a string in others (
age: 36/age: forty), or a tag used on both mappings and single values. Each one links to the odd uses out.
Show in tree filters the tree to the type's nodes, which lights them up. Only explicitly tagged nodes are profiled.
Several documents in one file
A YAML file can hold several documents, separated by --- (and optionally
ended by ...), as Kubernetes manifests often do. Each document is its own
tree: the Tree tab shows a document switcher — “Document 2 of 5 — mapping ·
4 keys” — and the Tree, Schema and Copy as JSON work on the document chosen there.
The status bar, the warnings and the Types tab cover them all.
Anchors do not carry from one document to the next: an alias always refers to an anchor earlier in its own document.
Inferring a JSON Schema
The Schema tab shows a JSON Schema inferred from the current document —
the shape of its JSON form, with aliases read as what they stand for and tags left
out. Each position gets a type (a list where several were seen), mappings
get their properties and the keys every one of them has as
required, and lists get one items schema that covers every element.
Integers are integer until one number at that position has a fraction.
Choose the draft (2020-12 or 07), whether to list required keys, and whether to write the schema as JSON or as YAML. Copy takes it. The schema is a starting point: it says what this sample has, not what every valid document may have.
Comparing two documents (Diff)
The Diff tab splits the editor into two panes, Left and Right. Whatever was in the Text editor goes into Left; Right starts empty (and keeps what you put in it while you visit the other tabs). Switching back to Text or Tree carries on with the Left pane's YAML.
The comparison is about content, never order:
- Mapping entries match by key.
- List items match by content.
[1, 2]and[2, 1]are the same list. Equal items pair first; of what is left, a mapping pairs with the most similar mapping on the other side — shared keys and equal values count, and an equalid(oruuid,key,code…) pairs two records outright. - Several documents compare as a set: their order does not matter either.
- Values compare as values.
"3"and3are a type change;1.0,1and0x1are the same number;'a',"a"andaare the same string. An alias compares as what it stands for; a tag is part of the value, so!Ref aandadiffer.
Each difference is highlighted on its row and marked in the gutter:
| Mark | Colour | Means |
|---|---|---|
| − | red | missing — only on the Left |
| + | green | added — only on the Right |
| ~ | amber | changed — same key (or paired item), different value |
| ≠ | purple | type change — e.g. a string on one side, a number on the other |
The status bar sums them up — “3 differences: 1 added, 1 missing, 1 changed” — or says Documents match. ▲/▼ (or Alt+↑ / Alt+↓) step from one difference to the next, and the two panes scroll together.
Both panes are editors
Type, paste and delete in either pane. The panes are compared again about 1.5 seconds after you stop typing. If a pane's YAML is invalid at that point, the status bar shows the parser's exact error for that pane (click it to jump there), and the last good comparison stays on screen until it is fixed.
Block YAML is compared as you wrote it — one entry per line is what the rows line
up on. JSON, or YAML written in flow style ({…} on one line), is laid out in
block style when it is pasted or loaded, so its entries can line up too. Each pane has
its own Format, Fix paste, Copy, Clear and
Load from file buttons, and smart paste works in each one.
Placeholders
Where one side has an entry the other lacks, the other side shows a hatched placeholder row, so both panes stay aligned and the gap is easy to see. Placeholders are only drawn: they are never part of the pane's YAML, and they are left out of Copy, of a copy or cut you make yourself, and of the text carried back to the Text tab.
Typing into a placeholder turns it into a real line on the first keystroke, indented like the line opposite. Paste the missing entry in, pause, and the difference disappears.
Reorder
Reorder ( Left master | [Off] | Right master )
- Off (the default): each pane keeps its own order.
- Left master: the Right pane is rewritten in Left's order — keys and list items, at every level — so only the real differences stand out.
- Right master: the same the other way round.
Reordering changes order only: every value keeps its source text. (The rewritten pane is written out fresh, so its comments are not carried over.) It keeps following the master as you edit the master; once you edit it yourself, it is left alone. Moving back to Off restores its original order, unless you have edited it.
Which YAML
YamlHippo reads YAML 1.2 with its core schema, and is checked against the official YAML test suite. Unquoted values are typed by these rules only:
| Type | Written as |
|---|---|
| null | ~, null, Null, NULL, or nothing |
| boolean | true, True, TRUE, false, False, FALSE |
| integer | 42, -7, 0o17, 0x1F |
| number | 3.14, 6.02e23, .5, .inf, -.inf, .nan |
| string | everything else — including yes, no, 0755 read as octal, and 1:30 (see Warnings) |
Standard tags set the type (!!str 123 is a string, !!int "7" a number);
custom tags are kept and not acted on. << merge keys belong to YAML 1.1 and
are shown as an ordinary key, holding the alias.
A few things the spec forbids are accepted because real files do them and the common parsers accept them too — notably a flow collection whose continuation lines are not indented deeper than its key.
Keyboard
| Where | Key | Does |
|---|---|---|
| Text, or a Diff pane | Ctrl/⌘+Enter | Format |
| Tabs | ← → | Switch between Tree, Text, Types, Schema and Diff |
| Diff | Alt+↑ / Alt+↓ | Previous / next difference |
| Tree tab | / | Focus the filter |
| Filter | Enter / Shift+Enter | Next / previous match |
| Filter or tree | Esc | Clear the filter |
| Warnings list | Esc | Close it |
| Types list | ↑ ↓ | Previous / next type |
| Tree | ↑ ↓ | Previous / next row |
| Tree | → | Open a collection, or move into it |
| Tree | ← | Close a collection, or move to its parent |
| Tree | Enter or Space | Select the row |
| Tree | Home / End | First / last row |
Themes
The theme follows your system's light or dark setting. The button in the header cycles System → Light → Dark, and the choice is remembered — this guide follows it too.
Clicking the hippo at the top left opens About. Support YamlHippo… there opens GitHub Sponsors, for anyone who wants to chip in.
Error reference
Every error YamlHippo reports, with an example that causes it and how to fix it.
| Code | Example | What it means | Fix |
|---|---|---|---|
INDENT_MISMATCH | a line at 3 spaces among siblings at 2; a dedent to 2 between levels at 0 and 4 | The line's indentation matches no open block. The message says how deep it is and how deep its siblings are; the hint says where they are. | Line it up with the entries it belongs with. |
TAB_INDENT | a tab before key: | YAML indentation must be spaces. | Replace the tab with spaces — Fix paste does the whole document. |
MAPPING_VALUE_NOT_ALLOWED | title: Note: read this | A : where no key can start: after a value on the same line, or after a value that ran on from the line above. | Quote the value if the colon is part of it, or move the nested mapping to its own line. |
EXPECTED_COLON | version alone on a line in a mapping | A line at the mapping's indentation must be key: value. | Add the :, or indent the line if it continues the value above. |
EXPECTED_KEY | - item at a mapping's indentation; 'it's' | Something other than the next key where a mapping continues. | Indent the list under its key; inside '…' write a quote as ''. |
EXPECTED_ENTRY | key: value at a list's indentation | Something other than the next - entry where a list continues. | Indent it into the entry above, or start it with - . |
ENTRY_NOT_ALLOWED | key: - a | A block list cannot start on its key's line. | Put the list on the next line, indented. |
EXPECTED_VALUE | [1, , 2] | A value was needed and something else was there. | Put a value in the gap, or remove the extra comma. |
UNTERMINATED_STRING | message: "Hello and nothing closes it | A quoted string has no closing quote. The error points at the opening quote. | Add the closing quote. |
BAD_ESCAPE | "C:\Users" | Inside "…" a backslash starts an escape, and this is not one (\U needs 8 hex digits). | Write \\ for a backslash, or use single quotes. |
EXPECTED_COMMA_OR_CLOSE | {a: 1 b: 2} | Inside [ ] or { }, after an item comes , or the closer. | Add the missing comma. |
MISMATCHED_CLOSE | [1, 2} | A closing bracket does not match the one that opened, or matches nothing. | Use the matching closer, or add the missing opener. |
UNCLOSED_CONTAINER | ports: [80, 443 then the next key | A [ or { was never closed. The error points at it, not at where the file ends. | Add the missing ] or }. |
BLOCK_IN_FLOW | [a, - b], {a: |} | Block syntax (- , |, >) inside [ ] or { }. | Write items with commas; quote text. |
RESERVED_CHAR | email: @me | @ and ` are reserved and cannot start a value. | Quote the value. |
UNEXPECTED_CHAR | a: %x, "b"#c | A character that cannot go here; a comment needs a space before its #. | Quote the value, or add the space. |
BAD_BLOCK_HEADER | |x, |0 | After | or > only +/-, a digit 1–9 and a comment may follow. | Fix the header. |
BAD_ANCHOR | & name, &a *b | An anchor or alias without a name, or an alias given an anchor or tag of its own. | Write &name with no space. |
UNKNOWN_ALIAS | *bse | An alias to an anchor not defined earlier in the document. | Fix the name, or define the anchor first. |
BAD_TAG | !!int many, !e!x | A value its standard tag cannot have, a tag on the wrong kind of node, or an undeclared tag handle. | Fix the value or the tag; declare handles with %TAG. |
BAD_DIRECTIVE | %YAML 2.0; a directive after a document | A directive that is malformed, unsupported, or not followed by ---. | End the previous document with ...; follow directives with ---. |
TRAILING_CONTENT | [1] then [2]; text after ... | More content after the document's one top-level value. | Start another document with ---, or indent it under a key. |
EMPTY_INPUT | only whitespace or comments | There is no YAML at all. Reported by Format and Copy as JSON. | Paste or type some YAML. |
MAX_DEPTH | 10,001 nested [ | Nesting deeper than 10,000 levels. | Almost certainly generated by mistake; flatten it. |
And the warnings, which do not stop the YAML being valid (see Warnings):
| Code | Example | Fix |
|---|---|---|
YAML11_BOOLEAN | country: NO | Quote it, or write true/false. |
SEXAGESIMAL | time: 1:30 | Quote it. |
YAML11_NUMBER | mode: 0755, 1_000 | Write 0o755, or quote it. |
VERSION_NUMBER | python: 3.10 | Quote it. |
NUMBER_PRECISION | 12345678901234567890 | If every digit matters, quote it. |
DUPLICATE_KEY | id twice in a mapping | Rename or remove one. |
DUPLICATE_ANCHOR | &x twice | Rename one. |
RUN_ON_VALUE | - a / - b | Line the entry up with its siblings. |
EMPTY_KEY | "": 1 | Give it its name. |