gohugoio/hugo · error
ellipsis must be a string
Error message
ellipsis must be a string
What it means
In the three-argument form `truncate LENGTH ELLIPSIS TEXT`, the middle argument is cast to a string with `cast.ToStringE`. If that value isn't string-convertible (e.g. a map, slice, or page object), Hugo reports that the ellipsis must be a string. This often really means the arguments are in the wrong order.
Source
Thrown at tpl/strings/truncate.go:62
func (ns *Namespace) Truncate(s any, options ...any) (template.HTML, error) {
length, err := cast.ToIntE(s)
if err != nil {
return "", err
}
var textParam any
var ellipsis string
switch len(options) {
case 0:
return "", errors.New("truncate requires a length and a string")
case 1:
textParam = options[0]
ellipsis = " …"
case 2:
textParam = options[1]
ellipsis, err = cast.ToStringE(options[0])
if err != nil {
return "", errors.New("ellipsis must be a string")
}
if _, ok := options[0].(template.HTML); !ok {
ellipsis = html.EscapeString(ellipsis)
}
default:
return "", errors.New("too many arguments passed to truncate")
}
text, err := cast.ToStringE(textParam)
if err != nil {
return "", errors.New("text must be a string")
}
_, isHTML := textParam.(template.HTML)
if utf8.RuneCountInString(text) <= length {
if isHTML {
return template.HTML(text), nilView on GitHub ↗ (pinned to 8a468df065)
Solutions
- Put the ellipsis (a plain string) second and the text last: `{{ truncate 30 "..." .Summary }}`
- If you don't need a custom ellipsis, use the two-argument form: `{{ truncate 30 .Summary }}` (default is " …")
- Pass `template.HTML` ellipsis via `safeHTML` if you need unescaped markup: `{{ truncate 30 ("<b>…</b>" | safeHTML) .Content }}`
Example fix
// before
{{ truncate 30 .Summary "..." }}
// after
{{ truncate 30 "..." .Summary }} Defensive patterns
Strategy: type-guard
Validate before calling
{{ $ell := default " …" .Params.ellipsis }}
{{ if ne (printf "%T" $ell) "string" }}{{ $ell = string $ell }}{{ end }}
{{ truncate 70 $ell .Summary }} Type guard
{{ $ell := .Params.ellipsis }}{{ if $ell }}{{ $ell = string $ell }}{{ else }}{{ $ell = " …" }}{{ end }} Try / catch
{{ with try (truncate 70 .ell .text) }}{{ if .Err }}{{ truncate 70 .text }}{{ else }}{{ .Value }}{{ end }}{{ end }} Prevention
- When passing three args to truncate, the middle one must be a string ellipsis — a number there means you meant it as length and misordered the call.
- Coerce configurable ellipses with `string` since data-file values may be typed otherwise.
- Rely on the default ellipsis (omit the argument) unless you genuinely need a custom one.
When it happens
Trigger: `{{ truncate 30 .Pages .Summary }}` or any call where the second of three arguments is a non-string type; commonly `{{ truncate 30 .Summary … }}` variants where a non-string got swapped into the ellipsis slot.
Common situations: Swapped ellipsis/text argument order (text belongs last); passing a dict or slice from a partial's context; assuming ellipsis is optional-last rather than optional-middle.
Related errors
- truncate requires a length and a string
- too many arguments passed to truncate
- symdiff: failed to convert value: %w
- need at least 2 arguments to append
- must provide a Resource and optionally an options map
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/017ee1141d3d70dc.json.
Report an issue: GitHub ↗.