{"id":"cb550488d4006ef6","repo":"gohugoio/hugo","slug":"slice-bounds-out-of-range","errorCode":null,"errorMessage":"slice bounds out of range","messagePattern":"slice bounds out of range","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tpl/strings/strings.go","lineNumber":346,"sourceCode":"\tif argNum > 0 {\n\t\tif argStart, err = cast.ToIntE(startEnd[0]); err != nil {\n\t\t\treturn \"\", errors.New(\"start argument must be integer\")\n\t\t}\n\t}\n\tif argNum > 1 {\n\t\tif argEnd, err = cast.ToIntE(startEnd[1]); err != nil {\n\t\t\treturn \"\", errors.New(\"end argument must be integer\")\n\t\t}\n\t}\n\n\tif argNum > 2 {\n\t\treturn \"\", errors.New(\"too many arguments\")\n\t}\n\n\tasRunes := []rune(aStr)\n\n\tif argNum > 0 && (argStart < 0 || argStart >= len(asRunes)) {\n\t\treturn \"\", errors.New(\"slice bounds out of range\")\n\t}\n\n\tif argNum == 2 {\n\t\tif argEnd < 0 || argEnd > len(asRunes) {\n\t\t\treturn \"\", errors.New(\"slice bounds out of range\")\n\t\t}\n\t\treturn string(asRunes[argStart:argEnd]), nil\n\t} else if argNum == 1 {\n\t\treturn string(asRunes[argStart:]), nil\n\t} else {\n\t\treturn string(asRunes[:]), nil\n\t}\n}\n\n// Split slices an input string into all substrings separated by delimiter.\nfunc (ns *Namespace) Split(a any, delimiter string) ([]string, error) {\n\taStr, err := cast.ToStringE(a)\n\tif err != nil {","sourceCodeStart":328,"sourceCodeEnd":364,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/tpl/strings/strings.go#L328-L364","documentation":"After parsing indices, `SliceString` validates them against the rune length of the string: the start must be in `[0, len)` and the end in `[0, len]`. Out-of-range or negative indices produce this error instead of a Go runtime panic, mirroring Go slice-bounds semantics on runes (not bytes).","triggerScenarios":"`{{ slicestr \"abc\" 5 }}` (start ≥ rune length), `{{ slicestr \"abc\" -1 }}` (negative start), `{{ slicestr \"abc\" 0 10 }}` (end > rune length), or start on an empty string.","commonSituations":"Slicing a title/summary shorter than the hard-coded index; computing indices from byte length (`len`) on multibyte UTF-8 text while `slicestr` counts runes; empty param strings on some pages; expecting Python-style negative indices, which `slicestr` doesn't support (use `substr` for negatives).","solutions":["Clamp or check the string length first: `{{ if gt (len $s) 3 }}{{ slicestr $s 0 3 }}{{ end }}`","Use `substr` if you need negative/from-the-end indexing: `{{ substr $s -3 }}`","Remember indices are rune-based; derive bounds from `strings.RuneCount` semantics (Hugo's `len` on strings counts runes) rather than byte offsets"],"exampleFix":"// before\n{{ slicestr .Title 0 10 }}  <!-- fails when .Title has < 10 runes -->\n// after\n{{ if ge (len .Title) 10 }}{{ slicestr .Title 0 10 }}{{ else }}{{ .Title }}{{ end }}","handlingStrategy":"validation","validationCode":"{{ $s := .s }}{{ $start := int .start }}{{ $end := int .end }}\n{{ $n := len $s }}\n{{ $start = math.Max (math.Min $start $n) (sub 0 $n) }}\n{{ $end = math.Min $end $n }}\n{{ if lt $start $end }}{{ substr $s $start $end }}{{ end }}","typeGuard":null,"tryCatchPattern":"{{ with try (substr .s .start .end) }}{{ if .Err }}{{ warnf \"substr out of range for %q\" .s }}{{ else }}{{ .Value }}{{ end }}{{ end }}","preventionTips":["Clamp start/end against `len $s` before calling substr, especially when indices are computed or come from content of varying length.","Remember substr counts runes/bytes of the actual string — short titles or empty summaries are the usual trigger.","Guard empty strings: `{{ if $s }}...{{ end }}` before slicing."],"tags":["hugo","templates","strings","bounds","unicode"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}