{"id":"664305ca40fc71e0","repo":"gohugoio/hugo","slug":"text-must-be-a-string","errorCode":null,"errorMessage":"text must be a string","messagePattern":"text must be a string","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tpl/strings/truncate.go","lineNumber":73,"sourceCode":"\tcase 1:\n\t\ttextParam = options[0]\n\t\tellipsis = \" …\"\n\tcase 2:\n\t\ttextParam = options[1]\n\t\tellipsis, err = cast.ToStringE(options[0])\n\t\tif err != nil {\n\t\t\treturn \"\", errors.New(\"ellipsis must be a string\")\n\t\t}\n\t\tif _, ok := options[0].(template.HTML); !ok {\n\t\t\tellipsis = html.EscapeString(ellipsis)\n\t\t}\n\tdefault:\n\t\treturn \"\", errors.New(\"too many arguments passed to truncate\")\n\t}\n\n\ttext, err := cast.ToStringE(textParam)\n\tif err != nil {\n\t\treturn \"\", errors.New(\"text must be a string\")\n\t}\n\n\t_, isHTML := textParam.(template.HTML)\n\n\tif utf8.RuneCountInString(text) <= length {\n\t\tif isHTML {\n\t\t\treturn template.HTML(text), nil\n\t\t}\n\t\treturn template.HTML(html.EscapeString(text)), nil\n\t}\n\n\ttags := []htmlTag{}\n\tvar lastWordIndex, lastNonSpace, currentLen, endTextPos, nextTag int\n\n\tfor i, r := range text {\n\t\tif i < nextTag {\n\t\t\tcontinue\n\t\t}","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/tpl/strings/truncate.go#L55-L91","documentation":"Hugo's `truncate` template function casts its text argument to a string with `cast.ToStringE` after parsing the length and optional ellipsis. If that cast fails — i.e. the value passed as the text is not string-convertible (a map, slice, Page object, etc.) — Truncate returns this error. It fires only after the length argument was successfully parsed as an int, so it specifically means the second (or third) argument is the wrong type.","triggerScenarios":"Calling `{{ truncate 50 .Params.items }}` where the value is a slice/map; passing arguments in the wrong order like `{{ truncate .Summary 50 }}` (string first, so length cast fails earlier, but e.g. `{{ truncate 50 \"...\" .Pages }}` hits this); piping a non-string value: `{{ .Pages | truncate 10 }}`.","commonSituations":"Front-matter params that are lists or maps instead of strings; piping a Page or Resource object instead of `.Content`/`.Summary`; refactoring a template and swapping argument order; a param that is sometimes a string and sometimes a list across content files.","solutions":["Ensure the text argument is a string: pass `.Summary`, `.Plain`, or a string param, not an object or slice.","Check argument order — it's `truncate LENGTH [ELLIPSIS] TEXT`; the text comes last.","Convert explicitly before truncating, e.g. `{{ truncate 50 (string .Params.title) }}` or `delimit` a slice into a string first."],"exampleFix":"<!-- before -->\n{{ truncate 50 .Params.tags }}\n<!-- after -->\n{{ truncate 50 (delimit .Params.tags \", \") }}","handlingStrategy":"type-guard","validationCode":"{{ $v := .Params.summary }}\n{{ if eq (printf \"%T\" $v) \"string\" }}{{ truncate 50 $v }}{{ end }}","typeGuard":"{{/* narrow to string before truncate */}}\n{{ $s := \"\" }}\n{{ with $v }}{{ $s = printf \"%v\" . }}{{ end }}\n{{ truncate 50 $s }}","tryCatchPattern":null,"preventionTips":["Pass only string values to truncate; convert numbers/slices first with printf \"%v\" or string","Remember truncate accepts template.HTML too, but not maps/slices","When truncating front-matter params, guard with `with` since missing params are nil"],"tags":["hugo","templates","type-error","strings"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}