Library / SDK
beevik/etree avatar
beevik/etree

etree gives Go an element tree without a DOM or an XPath engine

Parse, query and modify XML easily in go

1,670 stars190 forksGoBSD-2-Clause

At a glance

What is it?
beevik/etree is a pure Go package that represents XML as a mutable element tree with XPath-like path queries, modelled on Python's ElementTree and built on encoding/xml. It fits Go programs that need to read, reshape and write XML with no third party dependencies, and it is not a validating parser or a full XPath 1.0 implementation.
Who is it for?
Adopt etree when you want to reshape XML documents inside a Go program with zero external dependencies and a readable path syntax, especially when the documents are small enough that memory mapping the tree costs nothing. Skip it if you need schema validation, streaming for very large files, or full XPath semantics such as functions and axes.
Can I use it commercially?
Yes. BSD-2-Clause 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 29 days 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

A mutable tree on top of encoding/xml, with no third party code

etree's pitch is narrow on purpose. It expresses an XML document as a tree of elements you can traverse and change, and it depends only on the Go standard library.

The design was inspired by Python's ElementTree, and the resemblance shows in the naming: CreateElement, SelectElement, FindElements, Text, and attribute values read through SelectAttrValue with a fallback default. The alternative it displaces is usually a heavier DOM library or a full XPath implementation, both of which bring their own parsing model and their own set of surprises.

The package targets Go 1.23 and later, stated in the README and pinned in go.mod as go 1.23.0. The repository is small enough to read in an afternoon: etree.go, helpers.go, path.go and their tests sit at the root next to a RELEASE_NOTES.md. There is no subpackage to learn and no plugin system.

Building a document from nothing and writing it back indented

Creation starts with NewDocument, and processing instructions are first class rather than an afterthought. The README example emits both an XML declaration and an xml-stylesheet instruction before the root element:

go
doc := etree.NewDocument()
doc.CreateProcInst("xml", `version="1.0" encoding="UTF-8"`)
people := doc.CreateElement("People")
jon := people.CreateElement("Person")
jon.CreateAttr("name", "Jon")
doc.Indent(2)
doc.WriteTo(os.Stdout)

Indent takes a size and accepts spaces or tabs, which matters when the output has to be diffed in review or pasted into a ticket. Comments go through CreateComment. Output is not limited to stdout: the package writes to and reads from files, byte slices, strings and any io interface, so the same code serves a CLI tool and a server handler.

The auto-indent feature is also a small trap. Indentation is regenerated on write, so a document that was hand formatted with meaningful whitespace in text nodes comes back reformatted.

Reading a file and walking attributes without decoding into structs

The read path is one call, with the error checked rather than swallowed:

go
doc := etree.NewDocument()
if err := doc.ReadFromFile("bookstore.xml"); err != nil {
    panic(err)
}

Traversal has two levels of convenience. SelectElement and SelectElements operate on relative paths from the current element, while FindElements and FindElementsSeq search from the document root. Iteration comes in sequence variants, so a large document does not require materialising every match as a slice first.

Attributes are read by key with a default, which removes the usual nil check: title.SelectAttrValue("lang", "unknown") returns the fallback rather than an empty string, so a missing attribute is distinguishable from an empty one at the call site. Direct access is still available through the Attr field, where each entry carries Key and Value, which is what you want when an element may carry attributes you did not anticipate.

Path queries: double slash, predicates and precompiled paths

The path language is the part to learn. A leading double slash makes the search recursive at any depth, while a single dot keeps it relative, and predicates filter on attributes or child text:

go
for _, t := range doc.FindElementsSeq("//book[@category='WEB']/title") {
    fmt.Println("Title:", t.Text())
}
path := etree.MustCompilePath("./bookstore/book[p:price='49.99']/title")
for _, e := range doc.FindElementsPathSeq(path) {
    fmt.Println(e.Text())
}

Note the p: prefix in the price predicate. That is namespace handling: the bookstore example declares xmlns:p for prices, and the query has to address that namespace the same way the document declares it.

Positional selection works too, with ./bookstore/book[1]/* returning every child of the first book. When you expect to run the same query many times, compile it once with MustCompilePath and pass it to FindElementsPathSeq. MustCompilePath panics on a bad path, which is the intended trade: the path is a constant in your source, so a failure at init is better than silently matching nothing in production.

Where the XPath-like language stops

The README describes the queries as XPath-like, and the wording is doing real work. This is a subset, not an implementation of XPath 1.0. There is no documentation in the README of function calls such as string-length or contains, no axis support beyond the parent and child traversal implied by separators, and no namespace resolution rules beyond the prefixed name matching shown above.

If your existing knowledge is XPath from another ecosystem, expect to test every expression you port. If you need full conformance, you need a different dependency, and that is exactly the cost etree is avoiding.

Two other boundaries follow from the architecture. etree has no push parser path in the documented API.

Release cadence, licensing and adopting it safely

The repository is not archived and the last push was on 2026-09-23, the same day release v1.8.1 was tagged. v1.8.0 landed on 2026-09-06 and v1.7.1 on 2026-08-15, so releases are frequent enough that pinning to a tag rather than tracking main is the sensible default for anything you put in production.

The license is BSD-2-Clause, one of the most permissive and easiest to comply with: retain the notice and the disclaimer, and there is no copyleft obligation on code that merely links the package.

The upgrade surface is small in the best sense. Because there are no dependencies, there is no transitive version matrix to manage, and the only compatibility question is the Go version floor. If your build runs an older Go version, you are below the stated minimum and need to upgrade the toolchain before you can upgrade the library.

Editorial conclusion

Adopt etree when you want to reshape XML documents inside a Go program with zero external dependencies and a readable path syntax, especially when the documents are small enough that memory mapping the tree costs nothing. Skip it if you need schema validation, streaming for very large files, or full XPath semantics such as functions and axes. Before committing, run the examples in example_test.go against your own document, check the XPath subset in path.go for the expressions you rely on, and pin a tagged version such as v1.8.1 rather than tracking main.

Frequently asked questions

How do I parse an XML file with beevik/etree?

Create a document with etree.NewDocument() and call doc.ReadFromFile("bookstore.xml"), checking the returned error. The same Document type also reads from strings, byte slices and any io.Reader.

Does beevik/etree support full XPath?

No. The README describes lightweight XPath-like query APIs covering recursive search with a leading double slash, attribute predicates such as //book[@category='WEB'], positional selection like book[1], and namespaces through prefixed names. It does not document function calls or the wider XPath axes.

What Go version does beevik/etree require?

Go 1.23 and later. The README states that requirement and go.mod pins it as go 1.23.0.

Does beevik/etree have any third party dependencies?

None. The README states it is implemented in pure Go and depends only on standard library packages, and go.mod lists no requirements beyond the module line and the Go version.

Official sources

  1. beevik/etree on GitHub
  2. Issues
  3. License: BSD-2-Clause
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/beevik-etree.svg)](https://hysenlabs.com/projects/beevik-etree)

Was this useful?