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
- Pass a string field explicitly: `{{ strings.CountRunes .Content }}` or `.Plain`, not the page object itself.
- If the value is a slice you meant to join, use `{{ strings.CountRunes (delimit . " ") }}`.
- 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
- Pass strings or types implementing fmt.Stringer to string functions, not maps/slices
- Use `string $v` or `printf "%v" $v` to coerce explicitly before regex/string functions
- Don't pass whole page objects — pass `.Content`, `.Plain`, or `.Title`
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
- with 2 arguments, the first must be a slice of replacement p
- modulo operator can't be used with non integer value
- Pow operator can't be used with non-float value
- Round operator can't be used with non-float value
- Sqrt operator can't be used with non integer or float value
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/72362fe573e8d63e.json.
Report an issue: GitHub ↗.