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 ↗.