XML Tree Viewer
Navigate large XML as a collapsible tree — search names, see namespaces, copy any node's XPath, entirely on this device.
Paste XML, drop a file, or Load sample XML
Press ? for shortcuts
Do it offline:$ xmllint --format file.xml | less
How to use it
- Paste XML, drop a file, or load the sample. Short documents build as you type; past 2 MB the button carries the size — Parse 8.4 MB.
- Walk with the keyboard: ↓ and ↑ step through rows, → opens an element, ← closes it or climbs to its parent. The level menu refolds to depth 1, 2 or 3.
- Search names, values, or both — as typed or as a regular expression. Enter and Shift + Enter walk the hits, opening only the ancestors each one needs.
- Select a row for Copy XPath, Copy node — the original characters, tags included — and Copy text. Share folds your open branches into a link.
Paste a configuration file, a SOAP capture or a 40 MB export and read it as a tree: fold away the branches that do not matter, count the children of the one that does, and copy its exact address to point a colleague at it. The text you paste is never rewritten, and it never leaves this device.
Examples
<order id="A-1042">
<items>
<item sku="KB-701" qty="1"/>
<item sku="MS-220" qty="2"/>
<item sku="HD-119" qty="1"/>
</items>
</order>/order/items/item[3]/@skuCopy XPath on the sku of the third line item. A number appears only where a name repeats, which is why items carries none — the same convention libxml2 prints, so the address pastes straight into xmllint.
<p:doc xmlns:p="urn:example:p">
<p:note>Ship <b>today</b> please</p:note>
<p:raw><![CDATA[<not markup> & co]]></p:raw>
</p:doc>XPath /p:doc/p:raw/text()
Slash /p:doc/p:raw/#cdata[1]
Safe XPath /*[local-name()='doc']/*[local-name()='raw']/text()One row, three spellings. XPath has no step for a CDATA section, so it answers text(); the slash syntax keeps the #cdata marker; the safe form drops a prefix nothing else may have bound.
<project>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>2.0.13</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>33.2.0-jre</version>
</dependency>
</dependencies>
</project>/project/dependencies/dependency[1]/version
/project/dependencies/dependency[2]/versionSearching names only for version lands on the two elements, not the digits inside them; the values scope would trade those hits for 2.0.13 and 33.2.0-jre.
<Body>
<soap:Fault>
<faultstring>Read timed out</faultstring>
</soap:Fault>
</Body>XPath /Body/soap:Fault
Safe XPath /Body/*[local-name()='Fault']A fault copied out of its envelope: soap has no declaration left in scope. The row is badged unbound, and the safe spelling still resolves.
Every example on this page is executed against the same engine that powers the tool, on every build.
Gotchas worth knowing
A number appears only where a name repeats
Two dependency elements under one parent become dependency[1] and dependency[2]; a lone dependencies stays bare. Positions count from 1 among siblings matching the same step.
Comments, instructions and the DOCTYPE are rows
The declaration, comments, processing instructions and the doctype each get a row instead of being folded into chrome. The doctype is one opaque row, internal subset included: nothing it declares is fetched or expanded.
The gaps between tags are nodes too
Indentation between two elements is a text node, and the census counts it as one. Those rows stay hidden until a search matches one, so an element holding nothing else reads as a leaf.
Text previews at 200 characters, copies in full
A row shows the first 200 characters of a text node and says it was shortened; Copy text still hands over every character, and Copy node returns the original slice, CDATA wrapper included.
Ten thousand siblings arrive one thousand at a time
A parent with thousands of children opens with its first 1,000 rows and a load-more line, while its badge states how many children it can show.
Entities beyond the predefined five stay as written
The five named entities and numeric references decode in previews and in search. Any other reference is left exactly as written, badged and noted with its line, because resolving it would mean reading a document this page never opens.
Depth is measured over every kind of node
The status line reports the deepest row, not the deepest element, so text inside a root sits at depth 1. Nesting past 100,000 levels is refused; 10,000 build fine.
Common errors
Second root element '<b>' at line 1, column 5 — an XML document has exactly one root element; this looks like an XML fragment — wrap the elements in a single container.- The usual result of copying sibling elements out of a bigger file. Wrap them in any container and the tree opens; your bytes stay untouched either way.
Closing tag '</a>' at line 1, column 7 — '<b>' opened at line 1, column 4 is still open; did you forget '</b>'?- Two positions are reported because the mistake usually lives at the tag still open, not where the reader noticed it. Jump to error lands on that tag.
No root element at line 1, column 24 — probable cause: the document has only a prolog (declaration, comments or DOCTYPE); an XML document needs exactly one element.- A file of comments alone has no tree to draw. An empty box is no error, though — nothing pasted means nothing shown.
The search pattern is not a valid regular expression. Invalid regular expression: /[/: Unterminated character class- Regular-expression mode quotes the browser's own complaint on the second line and leaves the tree as it was, so a half-typed pattern never collapses what you had open.
FAQ
Is any of this document sent to a server?
No. 0 bytes uploaded, no accounts, no tracking cookies: a dropped file is read by the page rather than posted anywhere, and no external DTD or entity reference is ever fetched.
How big a file can it open?
Up to 50 MB. Past a quarter megabyte the pass runs on a background thread, and the tree holds positions into your text rather than a copy. Beyond that comes a plain refusal and a command to run instead.
Why do some addresses use local-name()?
Because a prefix means nothing away from its declaration. Where one is missing, or the tool you paste into cannot bind it, the safe spelling matches the local part instead.
Can I evaluate my own XPath expression here?
Not here — this page copies addresses rather than running queries. Search covers names and values; the XPath tester under related tools is where a full expression belongs.
Keyboard shortcuts
- ↑ / ↓
- Move between rows
- →
- Expand a row, or step into it
- ←
- Collapse a row, or climb to its parent
- Home / End
- Jump to the first or last row
- Enter
- Toggle the selected row
- A–Z
- Type-ahead by element name
- ⌘/Ctrl + Enter
- Parse a large document
- ⌘/Ctrl + S
- Download the document as a .xml file
- ⌘/Ctrl + C
- Copy the active row's source byte for byte, or the whole document when no row is active
- ⌘/Ctrl + Shift + C
- Force-copy the whole document, whatever row is active
- t
- Cycle the color theme
- ? or ⌘/Ctrl + /
- Open shortcut help
- Esc
- Close the open dialog