The query interface for the build graph
You're staring at a build failure that makes no sense. The compiler says a package exists in one place but not another. Or you just merged a dependency update and now you need to know exactly which version of golang.org/x/text is pulling in a transitive vulnerability. You could grep through go.mod, but that only shows direct dependencies. You could dig through the module cache, but that's slow and messy. The Go toolchain already knows the answer. It just needs you to ask the right question.
go list is the inspection tool for the Go build system. It queries package metadata, dependency versions, and file lists without compiling anything. Think of it as a read-only view into the build graph. The command runs the same dependency resolution and build constraint logic that go build uses, but instead of producing binaries, it outputs structured data about packages and modules. This means go list tells you exactly what the compiler sees, including the effects of build tags, environment variables, and module replacements.
Minimal example: listing packages
Here's the simplest way to see every package in your current module. The pattern ./... tells the tool to recurse through all subdirectories.
# List all packages in the current module recursively
# The ./... pattern matches the current directory and all nested packages
# This gives you a quick inventory of the project structure
go list ./...
The output is a list of import paths. Each line represents a package that the toolchain would build if you ran go build ./.... This is the starting point for almost every inspection task.
How go list resolves the graph
When you run go list, the toolchain starts by reading your go.mod file. It resolves the module graph, applying any replacements you've defined. Then it walks the directory tree, looking for .go files. For each directory containing Go files, it evaluates build constraints. If a file has //go:build linux and you're on macOS, go list skips that file, just like the compiler would.
The result is a precise list of packages that would be built in your current environment. This consistency is why go list is reliable for scripting. The output matches the build behavior. If go list says a package exists, go build can build it. If go list excludes a file due to a build tag, the compiler ignores it too.
go list also respects environment variables. If you set GOOS or GOARCH, the command adjusts the output to match the target platform. This lets you inspect cross-compilation scenarios without changing your machine's configuration.
# Check package structure for a different OS
# GOOS overrides the operating system for the query
# This lets you see which files would compile on Linux without switching your machine
GOOS=linux go list -f '{{.GoFiles}}' ./pkg/platform
The Go community treats go.mod as the single source of truth for dependencies. go list always respects the module graph defined there. If you change go.mod manually, go list reflects those changes immediately. This makes it a safe tool for verifying module state.
Formatting output with templates
The default output is just import paths. The -f flag unlocks the real power of go list by accepting Go template syntax. You can access fields like .ImportPath, .GoFiles, .Imports, and .Deps. This turns go list into a data extraction tool.
The template syntax comes from the standard library's text/template package. If you know Go templates, you can use functions like join, printf, and range. This makes the output highly customizable without writing external scripts.
# Use -f to format output with Go template syntax
# .ImportPath returns the full import path of the package
# .GoFiles returns a slice of the main source files included in the build
# This helps you see exactly which files are active for each package
go list -f '{{.ImportPath}}: {{.GoFiles}}' ./...
You can compare direct imports versus transitive dependencies. .Imports lists only the packages imported directly by the source files. .Deps lists all transitive dependencies required to build the package. This distinction helps you understand the full dependency footprint.
# Compare direct imports versus transitive dependencies
# .Imports lists only the packages imported directly by the source files
# .Deps lists all transitive dependencies required to build the package
# This distinction helps you understand the full dependency footprint
go list -f '{{.ImportPath}}: Imports={{len .Imports}} Deps={{len .Deps}}' ./...
Templates turn go list into a data pipeline. Learn the fields and script the output.
Inspecting modules and dependencies
By default, go list works on packages. Add -m to switch to module mode. This lets you inspect the module graph. Use all to see every dependency, direct and indirect. Combine with -json to get structured output. This is how you audit your supply chain or find transitive dependencies.
# Switch to module mode with -m
# 'all' lists the current module and all its dependencies
# -json outputs structured data for easy parsing by external tools
go list -m -json all
The -json flag outputs one JSON object per module. You can pipe this to jq or parse it in a script. This is essential for generating dependency reports or checking for vulnerable versions.
# Query available versions for a module
# The -versions flag fetches version history from the module proxy
# This is useful before running go get to see what versions exist
go list -m -versions github.com/gorilla/mux
Packages and modules are different axes. Use -m for modules, drop it for packages.
Debugging build constraints
Build constraints can be tricky. You might have a file that should compile but doesn't. go list helps you see what the compiler sees. If a package has no files matching your OS or architecture, the command returns no output for that package. This can be confusing if you expect results.
# Check build tags and files for a specific package
# .CgoFiles shows CGO files included in the build
# .GoFiles shows the standard Go source files
# This helps verify that build constraints are working as expected
go list -f '{{.ImportPath}}: GoFiles={{.GoFiles}} CgoFiles={{.CgoFiles}}' ./pkg/native
You can also inspect test files. .TestGoFiles includes _test.go files in the same package. .XTestGoFiles includes _test.go files in the external test package. This helps you verify which tests are included in the build.
# List test files for a package
# .TestGoFiles includes _test.go files in the same package
# .XTestGoFiles includes _test.go files in the external test package
# This helps you verify which tests are included in the build
go list -f '{{.ImportPath}}: {{.TestGoFiles}} {{.XTestGoFiles}}' ./...
Pitfalls and errors
go list can fail if the module graph is inconsistent. If you have a go.mod file but missing dependencies, the command might reject the query with go list: module ... requires ... which is not allowed. This happens when a dependency is missing from the module cache or the network is unreachable. Run go mod tidy to fix the graph before inspecting.
Another common issue is build constraints. If you list a package that has no files matching your OS or architecture, you get build constraints exclude all Go files in .... This isn't an error in your code. It just means the package is empty for your platform. The command returns no output for that package, which can be confusing if you expect results.
go list might download modules to resolve the graph. If you're in a restricted environment, use -mod=readonly to prevent network access.
# Inspect packages without downloading modules
# -mod=readonly prevents the tool from updating go.mod or downloading modules
# This is useful in CI environments where network access is restricted
go list -mod=readonly ./...
On very large projects, go list ./... might take time. It has to walk the graph. The toolchain caches results to speed up repeated queries. If the output seems stale, check the cache or run with -mod=mod to force a refresh.
go list shows the truth. Trust the output. If the output is wrong, the graph is wrong. Fix the graph first.
When to use go list
Use go list ./... when you need a quick inventory of all packages in your project. Use go list -m all when you want to see every dependency, including transitive ones. Use go list -f '{{.GoFiles}}' when you need to know which source files are actually included in the build. Use go list -json when you are writing a script that needs to parse package metadata programmatically. Use go list -m -versions when you need to find available versions of a module for an upgrade. Use go list -mod=readonly when you must inspect the graph without network access. Use go env when you need to check toolchain configuration rather than package structure.