XPath & JSONPath Tester
TextEverything runs locally in your browser — nothing is uploaded
Paste some content, write a path, and see what it selects. The common jobs: `//a/@href` for every link, `//img/@src` for every image URL, `//h2` for every heading — or `$.store.book[*].title` for every title in an API response, and `$..id` for every id at any depth. Why both syntaxes share one page: they have the same shape — a path plus a document gives you a list of results. Split into two tools, you would first have to decide whether the thing in your clipboard is JSON or XML, and that decision is pointless before you have pasted anything. Pick the syntax above instead. Both are defined subsets. What is supported is listed below, and anything outside it raises a clear “not supported” error rather than returning nothing — because “the syntax is not implemented” and “nothing matched” look identical on screen, and the former will have you re-checking a selector that was never wrong. JSONPath has no de-facto standard, and you should know that up front: `$..book[?(@.price<10)]` does not return the same thing across the JavaScript, Python and Java JSONPath libraries — they disagree on slice boundaries and on whether a descendant search includes the node itself. RFC 9535 only landed in 2024. This tool follows RFC 9535, so the result is good for local debugging; check how your target library behaves before pasting an expression into code. Why not implement full XPath: it means thirteen axes, namespaces, variables and a long list of functions — thousands of lines. The browser’s own `document.evaluate` would be instant, but it requires the algorithm to touch the DOM, and every transform on this site is a pure function so it can be tested in Node. This tool implements the common layer on top of the site’s own lightweight HTML parser. Parsing is forgiving: pasted HTML often has unclosed tags and missing quotes, and none of that should fail the whole query (the same parser backs HTML-to-Markdown).
Features
- One panel, two syntaxes: XPath for HTML / XML, JSONPath for JSON
- XPath axes: child `/` and descendant `//` — the two that extraction work needs
- XPath node tests: tag names, `*`, `@attribute` and `text()`, matched case-insensitively
- XPath predicates: `[1]`, `[last()]`, `[@a]`, `[@a="v"]`, `[contains(@a,"v")]`, `[starts-with(@a,"v")]` and more
- JSONPath: `.key`, `["key"]`, `[*]`, `..` descendant search, `[0]` with unions, and `[1:3]` slices
- JSONPath filters: `[?(@.price < 100)]` with `==` `!=` `<` `>` comparisons, `&&`, `||` and parentheses
- JSONPath can also print the location of each match (`$`-prefixed path), ready to paste into code
- Four outputs: text/value, HTML source, location, or a count
- Position predicates follow the real XPath semantics: `//a[1]` is the first `a` under each parent
- Every result carries a 1-based index and can be copied on its own; copied objects come out pretty-printed
- A sentence when nothing matched, rather than an empty panel
- Unsupported syntax errors out, so it can never be confused with an empty match
- Malformed HTML still parses — a missing closing tag does not fail the query
How to use
- Pick the syntax: XPath (HTML / XML) or JSONPath (JSON)
- Paste the source or the JSON and write an expression, such as //a/@href or $.store.book[*].title
- Choose the output: text/value, HTML source, location, or a count
- The result updates as you type; copy a single row with its button
FAQ
- Which XPath syntax is supported?
- The axes are child `/` and descendant `//`; node tests are tag names, `*`, `@attribute` and `text()`; predicates are `[1]`, `[last()]`, `[@a]`, `[@a="v"]`, `[text()="v"]`, `[contains(@a,"v")]`, `[starts-with(@a,"v")]` plus two joined with `and`; and the only function is an outer `count(...)`. Anything outside that raises “not supported” — for instance `position()>1`, `!=` comparisons, axes such as `ancestor::`, and namespaces. The reason for erroring rather than returning nothing is that the two look identical on screen, and the empty case would send you hunting for a mistake in a selector that was fine all along.
- Why does `//a[1]` select several elements?
- Because it selects the first `a` under each parent, not the first `a` in the document — that is what XPath specifies (`//a` means “the `a` children of any node”, and a position predicate applies within each group separately). For the document’s first match you would write `(//a)[1]`, which this tool does not support; take the first result row instead. This is the most commonly misunderstood part of XPath, which is why the standard semantics are kept rather than “fixed” for convenience.
- Why did my link extraction come back empty?
- Check three things: whether the attribute name is right (`href`, not `link`); whether the link lives on a different attribute (some sites use `data-href` or `onclick`); and whether the content is rendered by script — this tool parses the source you pasted, so if that source came from “view source” on a JavaScript-rendered page, the elements were never in it. Copy the rendered HTML from the Elements panel instead. The tool never fetches the URL, deliberately.
- Which JSONPath syntax is supported?
- The root `$` (optional), child access `.key` and `['key']`, wildcards `[*]` and `.*`, descendant search `..`, indices and unions `[0]` and `[0,2]`, slices `[1:3]`, and filter expressions `[?(@.price < 100)]` with existence tests, `==` `!=` `<` `<=` `>` `>=` comparisons, `&&`, `||`, parentheses and negation `!`. Anything else raises an error: function extensions (`length()`, `count()`, `match()`, `search()`, `value()`), regular expressions, and absolute `$`-rooted paths inside a filter.
- Why does my JSONPath differ from what the library in my code returns?
- Because JSONPath still has no single standard. RFC 9535 only arrived in 2024, and before that the JavaScript `jsonpath`, Python `jsonpath-ng` and Java Jayway libraries each went their own way, disagreeing on three points: slice boundaries (does `:3` include the third item?), whether a descendant search includes the node itself (can `$..a` select the top-level `a`?), and whether filters compare across types (`@.price < "10"` comparing a string to a number). This tool follows RFC 9535 throughout: slices are right-open, descendant search includes the node itself, and cross-type comparisons are false. So use it to confirm a path is right while debugging locally, then verify against real data in your target library before shipping it.
- What is the “location” output for?
- It prints, for each match, its path within the document, such as `$.store.book[2].title`. The usual direction is the reverse — you can see what a value looks like but need its position in the structure so you can hard-code a path to it. Values alone tell you that some results exist; locations tell you where they are. Note that a key containing special characters is written `['a.b']`, which is what keeps it distinct from `.a.b`.
Related tools
HTML Formatter
FormatFormat HTML online by tag level, keeping text and inline tags on one line and script or pre content untouched. Runs locally, with nothing uploaded.
JSON Formatter
FormatFormat, validate and minify JSON in a collapsible tree with syntax highlighting and line numbers. Search, edit, delete or convert to Python dicts.
HTML to Markdown
TextConvert HTML to Markdown: headings, lists, tables, links, images and code fences in GitHub style, with Markdown syntax characters escaped.
Regex Tester
TextTest a regex in the browser: live highlighting, capture groups, replace preview, a backtracking guard — and JavaScript, Python, Java, Go or PHP code.
Text Diff
TextCompare two blocks of text online and highlight added, removed and unchanged lines, so only the lines that changed are marked. Runs in your browser.
Word Counter
TextCount Chinese characters and English words separately, alongside characters, lines, real UTF-8 bytes, reading and speaking time, and SMS segment counts.