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), nil

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Put the ellipsis (a plain string) second and the text last: `{{ truncate 30 "..." .Summary }}`
  2. If you don't need a custom ellipsis, use the two-argument form: `{{ truncate 30 .Summary }}` (default is " …")
  3. 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 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


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