gohugoio/hugo · error

failed to parse URL for resource

Error message

failed to parse URL for resource %s: %w

What it means

Wrapped error from `Client.FromRemote` (remote.go:196-198) when `url.Parse(uri)` fails. Hugo requires a syntactically valid URL before doing anything else with the remote resource request. The error from the stdlib `url.Parse` is wrapped with the offending URI for context.

Solutions

  1. Ensure the URL has an explicit scheme and host (`https://...`).
  2. URL-encode dynamic path/query segments with `htmlEscape`/`urlquery` before assembling.
  3. Trim whitespace and strip smart quotes from frontmatter-supplied URLs.
  4. Pre-validate with a quick `url.Parse` test harness for frontmatter URLs.

Example fix

// template -- before
{{ $r := resources.GetRemote (printf "%s/%s" $base $slug) }}

// after
{{ $r := resources.GetRemote (printf "%s/%s" $base (urlquery $slug)) }}
Defensive patterns

Strategy: validation

Validate before calling

import "net/url"
func validateRemoteURL(s string) error {
	u, err := url.Parse(s)
	if err != nil { return err }
	if u.Scheme == "" || u.Host == "" {
		return fmt.Errorf("URL %q missing scheme or host", s)
	}
	return nil
}

Try / catch

// Template-side guard.
{{ $u := urls.Parse $raw }}
{{ if and $u.Scheme $u.Host }}{{ $r := resources.GetRemote $raw }}{{ end }}

Prevention

When it happens

Trigger: Calling `resources.GetRemote` / `FromRemote` with a malformed URL string: unescaped spaces, missing scheme (`example.com/x`), stray control characters, unbalanced brackets, or template interpolation injecting bad characters.

Common situations: Template builds URL via `printf` without escaping user input; missing `http://`/`https://` scheme; copy-paste from a rich-text editor inserting non-breaking spaces; query params with raw `&`/`#`; content frontmatter URL field with leading/trailing whitespace or stray quotes.

Understand the failure class

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/de6d2236d793dd62. Report an issue: GitHub.

Appendix: source

Thrown at resources/resource_factories/create/remote.go:198

				if time.Since(lastChange) < 10*time.Second {
					// The user is typing, check more often.
					return 0, nil
				}

				// Increase the interval to avoid hammering the server.
				interval += 1 * time.Second

				return interval, nil
			},
		})
}

// FromRemote expects one or n-parts of a URL to a resource
// If you provide multiple parts they will be joined together to the final URL.
func (c *Client) FromRemote(uri string, optionsm map[string]any) (resource.Resource, error) {
	rURL, err := url.Parse(uri)
	if err != nil {
		return nil, fmt.Errorf("failed to parse URL for resource %s: %w", uri, err)
	}

	method := "GET"
	if s, _, ok := hmaps.LookupEqualFold(optionsm, "method"); ok {
		method = strings.ToUpper(s.(string))
	}
	isHeadMethod := method == "HEAD"

	optionsm = maps.Clone(optionsm)

	// Extract timeout before computing cache keys: it only affects fetch behaviour,
	// not the cached content, so it must not influence the cache key.
	var perRequestTimeout time.Duration
	if v, k, ok := hmaps.LookupEqualFold(optionsm, "timeout"); ok {
		d, err := cast.ToDurationE(v)
		if err != nil {
			return nil, fmt.Errorf("invalid timeout for resource %s: %w", uri, err)
		}

View on GitHub (pinned to 52c9bd7908)