gohugoio/hugo · critical
can't make permalink from absolute link %q
Error message
can't make permalink from absolute link %q
What it means
`paths.MakePermalink` joins a base URL (usually the site's baseURL) with a relative link to form a permalink. If the second argument parses to a URL with a non-empty Host — i.e. it's already absolute like `https://example.com/foo` — joining is ambiguous and the function panics with this error. It's an internal invariant: callers must pass site-relative paths.
Source
Thrown at common/paths/url.go:71
// MakePermalink combines base URL with content path to create full URL paths.
// Example
//
// base: http://spf13.com/
// path: post/how-i-blog
// result: http://spf13.com/post/how-i-blog
func MakePermalink(host, plink string) *url.URL {
base, err := url.Parse(host)
if err != nil {
panic(err)
}
p, err := url.Parse(plink)
if err != nil {
panic(err)
}
if p.Host != "" {
panic(fmt.Errorf("can't make permalink from absolute link %q", plink))
}
base.Path = path.Join(base.Path, p.Path)
base.Fragment = p.Fragment
base.RawQuery = p.RawQuery
// path.Join will strip off the last /, so put it back if it was there.
hadTrailingSlash := (plink == "" && strings.HasSuffix(host, "/")) || strings.HasSuffix(p.Path, "/")
if hadTrailingSlash && !strings.HasSuffix(base.Path, "/") {
base.Path = base.Path + "/"
}
return base
}
// AddContextRoot adds the context root to an URL if it's not already set.
// For relative URL entries on sites with a base url with a context root set (i.e. http://example.com/mysite),
// relative URLs must not include the context root if canonifyURLs is enabled. But if it's disabled, it must be set.View on GitHub ↗ (pinned to 8a468df065)
Solutions
- Change the offending front-matter `url`/`slug` or permalink pattern to a site-relative path (starting with `/`), leaving the host to baseURL.
- Search content for absolute URLs in `url:` fields: `grep -rn 'url: *http' content/`.
- If you need an external link, use an external-link mechanism (e.g. a headless page or link in front matter consumed by templates), not the page URL.
Example fix
# before (front matter) url: https://example.com/post/how-i-blog/ # after url: /post/how-i-blog/
Defensive patterns
Strategy: validation
Validate before calling
u, err := url.Parse(link)
if err == nil && u.IsAbs() {
// already absolute: use as-is, do not pass to permalink construction
} Type guard
func isAbsURL(s string) bool {
u, err := url.Parse(s)
return err == nil && u.IsAbs()
} Try / catch
if err != nil && strings.Contains(err.Error(), "absolute link") {
// use the original URL directly instead of relURL/absURL-style processing
} Prevention
- Check IsAbs() before feeding links into permalink helpers; absolute URLs should bypass them
- In templates, branch on absolute vs relative links before calling relURL/absURL
- Store internal links as root-relative paths, not full URLs with scheme+host
When it happens
Trigger: Code paths that build permalinks (page URL resolution, `permalinks` config expansion, relURL/absURL-adjacent internals) receiving a link that already contains a scheme+host, e.g. a `url:` front-matter value or permalink pattern that expands to a full absolute URL.
Common situations: Setting `url: https://example.com/post/` in page front matter instead of `url: /post/`; permalink tokens or slugs containing a full URL; aliases or configuration values pasted with the domain included.
Related errors
- errPermalinkAttributeUnknown
- max depth exceeded
- failed to load config: %v
- template %s not parsed
- error in Parse: %w
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/ff2e41ea17b6a558.json.
Report an issue: GitHub ↗.