Go Vanity Import Paths¶
Published: Thursday, Jun 04, 2026

I’ve been seeing these import paths in Go projects:
go.uber.org/zapk8s.io/client-gogo.opentelemetry.io/otelgoogle.golang.org/grpc
These aren’t GitHub URLs. How does Go resolve them? I decided to investigate.
The Investigation¶
First, I checked what these domains actually return:
$ curl go.uber.org/zap
<!DOCTYPE html>
<html>
<head>
<meta name="go-import" content="go.uber.org/zap git https://github.com/uber-go/zap">
<meta name="go-source" content="go.uber.org/zap https://github.com/uber-go/zap https://github.com/uber-go/zap/tree/master{/dir} https://github.com/uber-go/zap/tree/master{/dir}/{file}#L{line}">
<meta http-equiv="refresh" content="0; url=https://pkg.go.dev/go.uber.org/zap">
</head>
<body>
Nothing to see here. Please <a href="https://pkg.go.dev/go.uber.org/zap">move along</a>.
</body>
</html>
Interesting. Just HTML with meta tags pointing to GitHub. Same pattern everywhere.
The Official Docs¶
Found the explanation in Remote import paths:
If the import path is not a known code hosting site and also lacks a version control qualifier, the go tool attempts to fetch the import over https/http and looks for a
<meta>tag in the document’s HTML<head>.The meta tag has the form:
<meta name="go-import" content="import-prefix vcs repo-root">
Note: Go adds ?go-get=1 to the request so servers can distinguish between Go tooling and regular browsers.
So the flow is:
- Go requests
https://go.uber.org/zap?go-get=1 - Parses
go-importmeta tag from HTML<head> - Clones from actual repository:
https://github.com/uber-go/zap
Simple HTTP + HTML meta tags. No magic.
Who Uses This¶
I checked several major Go projects:
- Kubernetes -
k8s.io/*βgithub.com/kubernetes/* - gRPC -
google.golang.org/grpcβgithub.com/grpc/grpc-go - Uber -
go.uber.org/*βgithub.com/uber-go/* - OpenTelemetry -
go.opentelemetry.io/*βgithub.com/open-telemetry/* - etcd -
go.etcd.io/*βgithub.com/etcd-io/etcd - Knative -
knative.dev/*βgithub.com/knative/*
All large organizations with many packages. Makes sense - if you’re managing 20+ Go repos, a custom domain gives you VCS flexibility.
The Go Implementation¶
I wanted to see how Go actually implements this. Found it in the Go source at src/cmd/go/internal/vcs/:
Building the URL (vcs.go):
func urlForImportPath(importPath string) (*url.URL, error) {
host, path := splitPath(importPath)
return &url.URL{Host: host, Path: path, RawQuery: "go-get=1"}, nil
}
Turns go.uber.org/zap β https://go.uber.org/zap?go-get=1.
Parsing HTML (discovery.go):
func parseMetaGoImports(r io.Reader) ([]metaImport, error) {
decoder := xml.NewDecoder(r)
decoder.Strict = false // Lenient parsing
// Scan for <meta name="go-import" content="...">
// Stop at </head> or <body>
}
Key details: case-insensitive HTML parsing, only scans <head> section, caches results, and has built-in patterns for GitHub/Bitbucket.
How to Implement It¶
Option 1: Static HTML (what most orgs use)
Host a simple HTML file on your custom domain:
<!DOCTYPE html>
<html>
<head>
<meta name="go-import" content="example.com/pkg git https://github.com/you/pkg">
<meta http-equiv="refresh" content="0; url=https://pkg.go.dev/example.com/pkg">
</head>
<body>
Nothing to see here. Please <a href="https://pkg.go.dev/example.com/pkg">move along</a>.
</body>
</html>
Host it on GitHub Pages or Netlify (both free with custom domains).
Option 2: Dynamic Server
Check the ?go-get=1 parameter:
func handler(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("go-get") == "1" {
// Go tooling gets meta tags
w.Header().Set("Content-Type", "text/html")
fmt.Fprintf(w, `<meta name="go-import" content="example.com/pkg git https://github.com/you/pkg">`)
return
}
// Humans get redirected
http.Redirect(w, r, "https://pkg.go.dev/example.com/pkg", http.StatusFound)
}
This is what Google uses for google.golang.org/grpc.
Existing Tools:
When It Makes Sense¶
After looking at who uses vanity import paths, the pattern is clear:
| Use Case | Makes Sense? |
|---|---|
| Large org with 10+ packages | β |
| Might migrate from GitHub to GitLab | β |
| Professional branding needed | β |
| Small personal project (1-3 repos) | β |
| Internal-only packages | β |
| GitHub URL is already clean | β |
I’d say if you’re managing fewer than 5 packages and don’t plan to move VCS providers, stick with GitHub URLs. The overhead isn’t worth it.
But if you’re Uber with 50+ Go repos, having them all under go.uber.org/* makes a lot of sense.
The Elegance¶
Vanity import paths are beautifully simple: HTML meta tags + HTTP redirects. No complex infrastructure.
The clever bit? Same endpoint, different behavior:
- Go tooling (
?go-get=1) β reads meta tags β clones from GitHub - Humans (no parameter) β redirected to documentation
It’s optional for GitHub but available for custom domains. Only use it when you need it.
The real win: migrate from GitHub β GitLab β self-hosted without breaking downstream imports. Maximum flexibility, minimal code.
References¶
- Remote import paths - Official spec
- Module VCS Discovery - How Go discovers repositories
- Vanity Import Paths in Go - Comprehensive guide
- Go source:
discovery.go,vcs.go
Appendix: The Caching Implementation¶
I mentioned Go caches the meta tag results. I was curious how that actually works, so I looked at the implementation in vcs.go:
// Global cache and mutex for thread-safe access
var (
fetchCacheMu sync.Mutex
fetchCache = map[string]fetchResult{} // key is the import prefix
)
var fetchGroup singleflight.Group // Deduplicates concurrent requests
func metaImportsForPrefix(importPrefix string, ...) (*url.URL, []metaImport, error) {
setCache := func(res fetchResult) (fetchResult, error) {
fetchCacheMu.Lock()
defer fetchCacheMu.Unlock()
fetchCache[importPrefix] = res
return res, nil
}
// Use singleflight to deduplicate concurrent requests
resi, _, _ := fetchGroup.Do(importPrefix, func() (resi any, err error) {
// Check cache first
fetchCacheMu.Lock()
if res, ok := fetchCache[importPrefix]; ok {
fetchCacheMu.Unlock()
return res, nil // Cache hit!
}
fetchCacheMu.Unlock()
// Cache miss - fetch from URL
url, err := urlForImportPath(importPrefix)
resp, err := web.Get(security, url)
defer resp.Body.Close()
imports, err := parseMetaGoImports(resp.Body, mod)
return setCache(fetchResult{url: url, imports: imports, err: err})
})
}
Two-level deduplication:
- Singleflight - If 10 goroutines request
go.uber.org/zapsimultaneously, only ONE HTTP request is made. Others wait for the result. - Simple map cache - Once fetched, subsequent requests for
go.uber.org/zapreturn cached results immediately (no HTTP call).
Example flow:
// First request: go.uber.org/zap
fetchCache = {} // empty
// β HTTP request to https://go.uber.org/zap?go-get=1
// β Store: {"go.uber.org/zap": {url, imports, err}}
// Second request: go.uber.org/zap
fetchCache = {"go.uber.org/zap": {...}}
// β Return cached result (no HTTP request)
// Concurrent requests:
// goroutine 1: requests go.uber.org/zap (makes HTTP call)
// goroutine 2: requests go.uber.org/zap (waits for goroutine 1)
// goroutine 3: requests go.uber.org/zap (waits for goroutine 1)
// β All three get the same result, only one HTTP request
The cache lives in memory for the duration of the go command execution. Not persisted to disk.
Simple but effective - prevents hammering vanity domains during dependency resolution.