xq is a terminal XML and HTML formatter that also extracts what you ask for
Command-line XML and HTML beautifier and content extractor
At a glance
- What is it?
- xq is a Go program that pretty prints XML or HTML with syntax highlighting and indentation, and can extract content with either XPath or CSS selectors, output the result as JSON, and page long output through the pager of your choice. It installs from most package managers, from Go or from a Docker image, and it has one dependency per job rather than a framework.
- Who is it for?
- Adopt xq when you need to read an XML or HTML document on a terminal and want to pull specific values out of it rather than just look at it, since the XPath and CSS selector support covers both the API-shaped and the page-shaped case. Do not adopt it for large files where you need streaming, since the tool parses the document into memory and the pager exists for the output rather than the input.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Format first, extract second
xq is a command-line XML and HTML beautifier and content extractor, and the feature list is four lines.
Syntax highlighting, automatic indentation and formatting, automatic pagination, and node content extraction.
The first three are the familiar behaviour of a pretty printer, with the addition that the output is coloured for the terminal, and the fourth is what makes it more than that. A tool that formats XML is a convenience. A tool that formats XML and then lets you ask for the value of one attribute is a query tool that happens to print nicely.
The implementation language is Go, the licence is MIT, and the last push was on 2026-10-12. The recent releases are v1.5.1 from 2026-08-23, v1.5.0 from 2026-08-11 and v1.4.0 from 2026-02-27, so the project is on a steady 1.x line with patch releases, and the badges cover a build workflow, coverage through codecov, a source line count, a Homebrew formula version and a MacPorts version.
The Homebrew and MacPorts badges are worth noting for a different reason. They mean the tool is in the package repositories of two of the larger desktop ecosystems, which is a sign that it has been used by enough people that packaging it was worth somebody's time.
XPath and CSS selectors, because the input is not always XML
The extraction section offers two query languages, and the split is by input format.
XPath is the -x parameter, and it works on XML. The first example extracts the text content of all nodes named city.
cat test/data/xml/unformatted.xml | xq -x //cityThe second extracts the value of an attribute named status belonging to a user.
cat test/data/xml/unformatted.xml | xq -x /user/@statusFor HTML, the -q parameter takes a CSS selector instead.
cat test/data/html/unformatted.html | xq -q "body > p"And there are two refinements. The -a option extracts an attribute value rather than the node content, so a script source attribute comes back rather than the script element.
cat test/data/html/unformatted.html | xq -q "head > script" -a "src"And the -n option extracts part of the HTML with the tags rather than only the text content.
cat test/data/html/unformatted.html | xq -n -q "head"That last one matters when you want to see the structure of a fragment rather than its text, which is the difference between a search result and a snippet.
The strict flag, and the exit code nobody expects
There is one behaviour in the tool that will bite you in a script, and the readme documents it clearly.
By default, a query that finds no matches produces empty output and a zero exit code.
That is a reasonable default for a person at a terminal, where an empty result is an answer. It is a bad default for a script, where the interesting question is whether the node exists.
The --strict option, with the short form -s, gives a non-zero exit code instead, and the readme gives the use case, for testing the presence of a node in scripts.
cat test/data/xml/unformatted.xml | xq -s -x //missing || echo "node not found"The strict flag is the sort of thing that separates a tool that is pleasant to use from a tool that is safe to automate, and it is worth noticing that it exists at all, because many extraction tools get this wrong in the other direction and fail loudly for a human who only wanted to look.
The formatting side has the same two identities. A plain run formats a file and highlights the syntax, and the -m flag does the same for HTML, and the -i flag formats in place, and multiple files can be given at once, and the input can come from standard input, so a curl of a URL piped into xq is the normal way to use it on something you did not download.
JSON output with a documented shape
The -j flag outputs the result as JSON, preserving the XML structure, and the readme then specifies exactly what that structure is.
cat test/data/xml/unformatted.xml | xq -jThe rules are five. XML elements become object keys. Attributes are prefixed with an @ sign. Text content is stored under #text if the element has attributes or child elements. Repeated elements are automatically converted to arrays. And elements with only text content are represented as strings.
That last rule is the one to think about, because it means the JSON shape varies with the document. An element that is just text is a string, and the same element with a child is an object with a #text key, so a consumer of the JSON cannot assume one shape. The rules are the same ones XML to JSON converters have used for years, and they are documented rather than invented, which is the important part.
The @ prefix and the #text key are conventions from the XML to JSON world, and they are what make the output usable with jq, which is the obvious next step for anyone who takes the JSON path.
Paging, and the two environment variables that control it
Long output is piped to a pager, and the details are the kind that matter when you run a tool in a pipeline.
The pager is used if it is defined via the XQ_PAGER or PAGER environment variable, and XQ_PAGER takes precedence.
Having two is for the same reason every tool has two: XQ_PAGER is the specific setting and PAGER is the general one, so a user can set the general one for their shell and the specific one for this tool without fighting.
The pager can be disabled with the --no-pager option.
xq --no-pager test/data/xml/unformatted.xmlThe automatic pagination in the feature list is the reason the tool is pleasant on a large XML file, where the alternative is a wall of text in a terminal that scrolls badly.
It is worth being clear that this is paging of the output only. The input is parsed as a whole, so the tool is for documents that fit in memory, and the pager is about reading the result rather than about streaming a huge input through it. The dependencies tell you that too, with an XML query library and a jQuery-like HTML library rather than a streaming parser.
Six ways to install it
The installation section is generous, which is the right treatment for a command line utility.
On macOS there are two, Homebrew and MacPorts.
brew install xqsudo port install xqOn Linux there is a custom installer, and a second form of it that lets you choose where it goes without sudo.
curl -sSL https://bit.ly/install-xq | sudo bashcurl -sSL https://bit.ly/install-xq | INSTALL_DIR=$(pwd) bashThe second form is the one to look at, because it writes into the current directory and does not need root, which matters on a locked down machine.
The distribution packages cover two more. Ubuntu 22.10 or higher has it in apt, and Fedora has it in dnf, with a repology link for the full list of distributions that package the utility.
If you have a Go toolchain, the Go install works.
go install github.com/sibprogrammer/xq@latestAnd there is a Dockerised environment for playing with it, using the compose file in the repository.
docker compose run --rm xq
xq /opt/examples/xml/unformatted.xmlThe compose service builds from the local Dockerfile, mounts the test data directory at /opt/examples, and opens standard input and a terminal so the container behaves like the real tool rather than a one-shot command.
The dependency list is one library per job
The go.mod file is short and it maps directly onto the features.
The query engines are antchfx/xmlquery at 1.5.2 and antchfx/xpath at 1.3.9 for the XPath side, and PuerkitoBio/goquery at 1.13.0 for the CSS selector side on HTML. The colour output is fatih/color at 1.19.0, the command line is spf13/cobra at 1.10.2 with spf13/pflag underneath, and the tests use stretchr/testify at 1.12.1. The standard library extensions are golang.org/x/net for the HTML parsing and golang.org/x/text for the text handling.
The Go version is 1.26.1, which puts a floor on the toolchain a contributor needs, and the module path is github.com/sibprogrammer/xq.
The container build mirrors the layout. The builder stage is golang:1.26 with CGO_ENABLED=0, so the binary is static, and it copies the module files first and runs go mod download so the dependency layer is cached separately from the source. The runtime stage is ubuntu:24.04 with the binary copied to /usr/local/bin, and the entrypoint is bash rather than xq, which is what lets the compose file run xq with different arguments against the mounted examples.
The repository also carries a .goreleaser.yml for the release binaries, a codecov.yml, a Dockerfile, a docker-compose.yml, a docs/ directory, a cmd/ directory, an internal/ directory, a scripts/ directory, a test/ directory and a main.go.
Editorial conclusion
Adopt xq when you need to read an XML or HTML document on a terminal and want to pull specific values out of it rather than just look at it, since the XPath and CSS selector support covers both the API-shaped and the page-shaped case. Do not adopt it for large files where you need streaming, since the tool parses the document into memory and the pager exists for the output rather than the input. Verify first that the pager you want is set through XQ_PAGER or PAGER, and use the strict flag in any script that checks whether a node exists, because the default behaviour on no match is empty output with a zero exit code.
Frequently asked questions
What does xq do?
It is a command-line XML and HTML beautifier and content extractor. It provides syntax highlighting, automatic indentation and formatting, automatic pagination, and node content extraction through XPath or CSS selectors.
How do I extract an attribute value with xq?
With the -a option, which extracts an attribute value instead of the node content, and with the query itself. For example, cat test/data/html/unformatted.html | xq -q "head > script" -a "src" pulls the src attribute, while -x takes an XPath expression for XML.
What happens when an xq query finds no match?
By default the output is empty and the exit code is zero. The --strict option, or -s, gives a non-zero exit code instead, which is what you want in a script that tests whether a node exists.
How do I get JSON out of xq?
Use the -j flag. The output preserves the XML structure, with elements as object keys, attributes prefixed with @, text content under #text when the element has attributes or child elements, repeated elements turned into arrays, and text-only elements represented as strings.
How do I install xq?
With brew install xq or sudo port install xq on macOS, the custom installer script on Linux, apt-get or dnf on the distributions that package it, go install github.com/sibprogrammer/xq@latest if you have a Go toolchain, or through the Docker compose environment in the repository.
How does xq page its output?
It pipes the result to a pager defined via the XQ_PAGER or PAGER environment variable, with XQ_PAGER taking precedence, and the pager can be turned off with the --no-pager option. This affects the output only, not how the input is read.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/sibprogrammer-xq)Was this useful?