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

  1. Change the offending front-matter `url`/`slug` or permalink pattern to a site-relative path (starting with `/`), leaving the host to baseURL.
  2. Search content for absolute URLs in `url:` fields: `grep -rn 'url: *http' content/`.
  3. 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

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


AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31). Data as JSON: /data/errors/ff2e41ea17b6a558.json. Report an issue: GitHub.