gohugoio/hugo · error

failed to unmarshal XML: %w

Error message

failed to unmarshal XML: %w

What it means

When unmarshaling XML data (data files or transform.Unmarshal), Hugo parses the document into a map with mxj (xml.NewMapXml) and then asks for the root element name via xmlRoot.Root(). This error wraps the failure of that Root() call — the parsed result has no identifiable single root element, typically because the document is empty or malformed at the top level.

Source

Thrown at parser/metadecoders/decoder.go:294

// UnmarshalTo unmarshals data in format f into v.
func (d Decoder) UnmarshalTo(data []byte, f Format, v any) error {
	var err error

	switch f {
	case ORG:
		err = d.unmarshalORG(data, v)
	case JSON:
		err = json.Unmarshal(data, v)
	case XML:
		var xmlRoot xml.Map
		xmlRoot, err = xml.NewMapXml(data)

		var xmlValue map[string]any
		if err == nil {
			xmlRootName, err := xmlRoot.Root()
			if err != nil {
				return toFileError(f, data, fmt.Errorf("failed to unmarshal XML: %w", err))
			}

			// Get the root value and verify it's a map
			rootValue := xmlRoot[xmlRootName]
			if rootValue == nil {
				return toFileError(f, data, fmt.Errorf("XML root element '%s' has no value", xmlRootName))
			}

			// Type check before conversion
			mapValue, ok := rootValue.(map[string]any)
			if !ok {
				return toFileError(f, data, fmt.Errorf("XML root element '%s' must be a map/object, got %T", xmlRootName, rootValue))
			}
			xmlValue = mapValue
		}

		switch v := v.(type) {
		case *map[string]any:

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Inspect the actual XML content being parsed (the wrapped mxj error and the file context in the error output identify it) and ensure it contains exactly one root element.
  2. For remote data, check the response before unmarshaling: verify `.Err` and status on the GetRemote resource and guard against empty `.Content`.
  3. Validate the file with an XML linter (`xmllint --noout file.xml`) and fix the malformation or re-download the truncated file.

Example fix

{{/* before */}}
{{ $data := resources.GetRemote $url | transform.Unmarshal }}

{{/* after */}}
{{ $r := resources.GetRemote $url }}
{{ if $r.Err }}{{ errorf "fetch failed: %s" $r.Err }}
{{ else if not $r.Content }}{{ errorf "empty response from %s" $url }}
{{ else }}{{ $data := $r | transform.Unmarshal }}{{ end }}
Defensive patterns

Strategy: validation

Validate before calling

// Well-formedness check before handing XML to Hugo
d := xml.NewDecoder(bytes.NewReader(data))
for { if _, err := d.Token(); err == io.EOF { break } else if err != nil { return fmt.Errorf("bad XML: %w", err) } }

Try / catch

if err != nil && strings.Contains(err.Error(), "failed to unmarshal XML") {
    // wraps the parser error — inspect errors.Unwrap(err) for line/position info
}

Prevention

When it happens

Trigger: Calling `transform.Unmarshal` on an XML string, or loading a `.xml` data file, where xml.NewMapXml succeeded (or returned partial data) but Root() errors — e.g. an empty document, only an XML declaration/comments with no element, or content that yields no root key in the map.

Common situations: Fetching a remote feed with `resources.GetRemote` that returns an empty body or an HTML error page and piping it into transform.Unmarshal; truncated XML files from failed downloads; data files accidentally saved empty.

Related errors


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