adrg/xdg and the case against hardcoded config paths
Go implementation of the XDG Base Directory Specification and XDG user directories
At a glance
- What is it?
- adrg/xdg is a Go library that resolves XDG base directory and user directory locations on Unix, macOS, Windows and Plan 9. It replaces hardcoded paths in application code with one set of variables per platform, including Known Folders on Windows.
- Who is it for?
- Use adrg/xdg when your Go program has to read or write data, configuration, cache or state files and wants to behave correctly on more than one operating system, particularly on Windows where the XDG environment variables are usually absent. Do not reach for it if your application needs a single fixed location everywhere, or if you need directory creation, file naming or per-user desktop integration that the specification does not cover.
- 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 4 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 21, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Why a path that works on Linux breaks on Windows
The XDG Base Directory Specification defines a set of standard locations for storing application data, configuration, state and cache files. The project description is blunt about the reason to follow it: for portability and flexibility, applications should use the XDG defined locations instead of hardcoding paths. That is the entire problem this library exists to solve, and it is a smaller problem than it first looks. A Go program that writes a config file to a literal path like a dot directory in the home folder is fine on one operating system and wrong on the others, and the fix is usually a small set of variables rather than a large abstraction.
adrg/xdg is aimed at engineers writing those programs. It is a library, not a command line tool, so nothing is installed to run and there is no daemon to configure. The work happens inside the process: the package computes the locations and hands them back as values your code uses. The spec it implements is a freedesktop one, but the package extends past it. Alongside the base directories it also exposes the well known user directories, the non-standard XDG_BIN_HOME directory, and other common directories such as fonts and applications. That extra coverage is the part most Go applications actually want, since a tool that caches a font or writes an executable has no use for a bare config path.
One table, three different sets of defaults
The defaults are where this kind of library either earns its place or quietly gets in the way, and the project puts them in a single table split by platform. On Unix-like systems the values are the familiar ones. XDG_DATA_HOME resolves to a share directory under the home folder, XDG_CONFIG_HOME to a config directory under the home folder, XDG_STATE_HOME to a state directory under the home folder, XDG_CACHE_HOME to a cache directory under the home folder, XDG_DATA_DIRS to system wide share directories, and XDG_CONFIG_DIRS to a single system wide config directory.
macOS gets its own answers, and they are not the Unix ones. The macOS column maps the data, config and state variables to Application Support inside the home folder, the cache variable to a Caches directory inside the home folder, and adds a Preferences directory to the config directories list. Plan 9 has a third set again, with a lib directory under the home folder standing in for several of the home scoped variables and separate lib, lib/state and lib/cache paths.
The read for an engineer is that XDG is not one rule set wearing different hats. If your program assumes the Unix layout and runs on macOS, the home scoped values land in a different place, and your migration story changes shape.
Windows falls through to Known Folders
On Windows the XDG environment variables are usually not set at all, so a library that only read the environment would hand back empty strings and force every caller to invent a fallback. adrg/xdg handles this by using Known Folders as the defaults on that platform. The Windows implementation lives in its own source file, alongside separate files for darwin, plan9 and the Unix case, which is how the platform split is enforced at build time rather than at runtime.
The payoff is redirection. Known Folders are the shell's own view of where a profile, cache or application data actually live, so if a user or a corporate policy has moved that content, the paths the library returns follow the move. The project states that appropriate locations are used for common folders which may have been redirected. Sensible fallback locations are used for the folders that are not set.
What this means for a caller is that the difference between Windows and Unix is not a special case you must handle yourself. The same variable name that points at a config directory on Linux points at the matching Windows location on Windows. The cost is that on Windows the values are not the ones a Linux developer would guess, so anything that hardcodes a path alongside the variable defeats the whole exercise.
Installing the module and pinning its Go version
Installation is a single command, taken from the project README. There is no binary to download and nothing to place on a path.
go get github.com/adrg/xdgThe module declares its own requirements in a go.mod file, reproduced here as the repository holds it.
module github.com/adrg/xdg
go 1.25.0
require (
github.com/stretchr/testify v1.12.1
golang.org/x/sys v0.45.0
)Two things in that file matter for adoption. The go directive asks for Go 1.25.0, so a toolchain older than that will refuse the module, which matters for a team still on an earlier release. The runtime dependency list is short, with golang.org/x/sys as a direct requirement and testify, used by the test suite, pulling in a YAML package indirectly. A small dependency footprint is worth checking before you add a path resolution library to a release build.
For a first real use, the pattern is to import the module path in your program and read the resolved location of the directory you need, rather than concatenating a home directory yourself. The public API is published on pkg.go.dev at the address the README links to, and that page is where the full list of exported variables and the user directory names live.
XDG_BIN_HOME and the directories the specification never mentions
The specification covers base directories, and the package goes further in ways that are worth knowing before you rely on them. Support for the non-standard XDG_BIN_HOME directory is called out separately in the project description, which is the clearest signal that this library carries practical extras rather than a strict reading of the spec. Common directories such as fonts and applications are included on the same basis.
That makes adrg/xdg useful for a class of program the base directory spec alone barely serves. A tool that installs a helper executable, resolves a font file, or wants to know where desktop applications are registered gets those locations from the same package, in the same style, with the same per platform defaults. A separate library for each of those would mean a separate set of platform edge cases, and the duplication is exactly where path bugs come from.
The trade-off is that the surface area grows past the specification, and the specification is the part with a stable, externally defined contract. Where XDG_BIN_HOME is non-standard, its behavior is defined by this project alone, and the same is true of the font and application directory handling. Treat the spec defined variables as the stable core and the extras as conveniences you check against the current documentation.
What the project does not document
The documentation is thin in a way that affects how you evaluate the library. The README installs the package, shows the platform default table, states the supported operating systems, and links to pkg.go.dev for everything else. It does not document rollback, and more relevantly it does not document whether the package creates any of the directories it returns. The project description says the package provides locations, and nothing in the documentation says that a returned path is guaranteed to exist on disk.
There is also no statement about file naming conventions. The specification defines where directories live, not what a file inside them should be called, so any naming scheme remains your responsibility.
Another silence worth noting: the documentation does not describe how the library behaves when an XDG variable is set to a relative path, or to a path that does not exist. The stated behavior covers variables that are empty or not present in the environment, and for those the package applies defaults. Everything outside that case, including a variable that is present but points somewhere unusual, is not covered by the description.
The practical response is to treat the returned values as locations, not as promises, and to create or check them yourself at the point of use.
Reading the environment yourself instead of adding a dependency
The obvious alternative is to read the environment variables directly in your own code and skip the library entirely. That approach is genuinely competitive for a narrow case: a program that runs only on Unix, where the XDG variables are typically defined and the defaults are three or four path segments you could write once. It is simpler, adds nothing to your module graph, and keeps the resolution rules visible in your own source.
The difference appears as soon as the target set widens. Reading the environment on Windows returns nothing, because the XDG variables are usually not set there, and every Windows path rule, including Known Folders and the handling of redirected folders, becomes code you own and must maintain. The library makes that a platform file, with separate implementations for darwin, plan9, windows and the Unix case, and the same per platform defaults apply to the user directories and the extras.
So the decision is not library versus no library, it is whether your program is single platform. For a cross platform Go tool, the per platform source files are the argument for depending on the package rather than reimplementing the same four tables in your own utility package.
Release history, licence, and the cost of staying current
The repository is not archived, and the last push was on 2026-10-19. The recent tagged releases are v0.5.3 on 2024-10-31, v0.5.2 on 2024-10-29, and v0.5.1 on 2024-10-17, so the published version numbers have not moved much in the two years since those tags while commits to the default branch have continued. The two close together patch releases in October 2024 suggest that the remaining v0.5.x line is where the project works.
The licence is MIT, which is permissive enough that the cost of adopting the library is licensing noise rather than a negotiation. There is no copyleft obligation to satisfy and no source disclosure requirement attached to code that merely links the package.
The upgrade cost is low because the interface is a set of path values rather than a framework with hooks and lifecycle. The real maintenance question is not the library but the specification it tracks: if the freedesktop base directory spec changes, the set of variables your program depends on can change too, and the package version is the point at which you would pick that up. The repository has a code of conduct, a contributing guide and a test workflow with a coverage badge, which is a reasonable shape for a project of this size, and it means the platform defaults have explicit per platform test files to guard them.
Editorial conclusion
Use adrg/xdg when your Go program has to read or write data, configuration, cache or state files and wants to behave correctly on more than one operating system, particularly on Windows where the XDG environment variables are usually absent. Do not reach for it if your application needs a single fixed location everywhere, or if you need directory creation, file naming or per-user desktop integration that the specification does not cover. Before adopting, check the API surface on pkg.go.dev, because the project points there for full documentation and the README shows only the install command, and confirm the returned paths on Windows against Known Folders that may have been redirected.
Frequently asked questions
What does adrg/xdg do exactly?
It is a Go implementation of the XDG Base Directory Specification and of the XDG user directories. It provides standard locations for application data, configuration, state and cache files so applications can avoid hardcoding paths.
Which operating systems does adrg/xdg support?
The project states support for most flavors of Unix, plus Windows, macOS and Plan 9. Each platform has its own source file and its own set of default locations.
How do I install adrg/xdg in a Go project?
Run `go get github.com/adrg/xdg` as the README instructs. The module requires Go 1.25.0 and lists golang.org/x/sys v0.45.0 among its requirements.
Does adrg/xdg return sensible paths on Windows where XDG variables are missing?
Yes. The package relies on Windows Known Folders as defaults when the environment variables are not set, and it follows folders that have been redirected. Sensible fallback locations are used for folders that are not set.
Where is the full API for adrg/xdg documented?
The README points to pkg.go.dev at the homepage address for full documentation. The README itself covers the install command, the platform default table and the supported operating systems.
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/adrg-xdg)Was this useful?