gohugoio/hugo · error

failed to convert content to string: %w

Error message

failed to convert content to string: %w

What it means

Hugo's `strings.CountRunes` (and the identically-messaged `strings.RuneCount` / `strings.CountWords`) first converts its argument to a string with `cast.ToStringE`. That cast succeeds for strings, numbers, template.HTML, and fmt.Stringer types, but fails for maps, slices, Page objects, and other composites — producing this wrapped error with the underlying cast failure as `%w`.

Source

Thrown at tpl/strings/strings.go:57

	return &Namespace{
		deps:          d,
		replacerCache: hmaps.NewCacheWithOptions[string, *strings.Replacer](hmaps.CacheOptions{Size: 100}),
	}
}

// Namespace provides template functions for the "strings" namespace.
// Most functions mimic the Go stdlib, but the order of the parameters may be
// different to ease their use in the Go template system.
type Namespace struct {
	deps          *deps.Deps
	replacerCache *hmaps.Cache[string, *strings.Replacer]
}

// CountRunes returns the number of runes in s, excluding whitespace.
func (ns *Namespace) CountRunes(s any) (int, error) {
	ss, err := cast.ToStringE(s)
	if err != nil {
		return 0, fmt.Errorf("failed to convert content to string: %w", err)
	}

	counter := 0
	for _, r := range tpl.StripHTML(ss) {
		if !helpers.IsWhitespace(r) {
			counter++
		}
	}

	return counter, nil
}

// RuneCount returns the number of runes in s.
func (ns *Namespace) RuneCount(s any) (int, error) {
	ss, err := cast.ToStringE(s)
	if err != nil {
		return 0, fmt.Errorf("failed to convert content to string: %w", err)
	}

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Pass a string field explicitly: `{{ strings.CountRunes .Content }}` or `.Plain`, not the page object itself.
  2. If the value is a slice you meant to join, use `{{ strings.CountRunes (delimit . " ") }}`.
  3. Inspect the wrapped error text — it names the actual Go type that failed to convert — and fix that variable at its source.

Example fix

<!-- before -->
{{ strings.CountRunes . }}
<!-- after -->
{{ strings.CountRunes .Content }}
Defensive patterns

Strategy: try-catch

Validate before calling

{{ if or (eq (printf "%T" $v) "string") ($v | printf "%v") }}{{ findRE $pattern (string $v) }}{{ end }}

Type guard

func isStringable(v any) bool {
    _, err := cast.ToStringE(v)
    return err == nil
}

Try / catch

s, err := cast.ToStringE(content)
if err != nil {
    return nil, fmt.Errorf("content not stringable: %w", err)
}

Prevention

When it happens

Trigger: Calling `{{ strings.CountRunes X }}`, `{{ strings.RuneCount X }}`, or `{{ countwords X }}` where X is not string-convertible: passing `.Pages`, a dict/slice, or a raw Page object instead of `.Content`/`.Plain`.

Common situations: Passing `.` (a Page) instead of `.Content` inside a template; piping the result of `where`/`slice` into a counting function; passing `.Params.tags` (a slice) where a single string was intended; reading-time or word-count partials applied to the wrong context variable.

Related errors


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