gohugoio/hugo · error

slice bounds out of range

Error message

slice bounds out of range

What it means

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).

Source

Thrown at tpl/strings/strings.go:346

	if argNum > 0 {
		if argStart, err = cast.ToIntE(startEnd[0]); err != nil {
			return "", errors.New("start argument must be integer")
		}
	}
	if argNum > 1 {
		if argEnd, err = cast.ToIntE(startEnd[1]); err != nil {
			return "", errors.New("end argument must be integer")
		}
	}

	if argNum > 2 {
		return "", errors.New("too many arguments")
	}

	asRunes := []rune(aStr)

	if argNum > 0 && (argStart < 0 || argStart >= len(asRunes)) {
		return "", errors.New("slice bounds out of range")
	}

	if argNum == 2 {
		if argEnd < 0 || argEnd > len(asRunes) {
			return "", errors.New("slice bounds out of range")
		}
		return string(asRunes[argStart:argEnd]), nil
	} else if argNum == 1 {
		return string(asRunes[argStart:]), nil
	} else {
		return string(asRunes[:]), nil
	}
}

// Split slices an input string into all substrings separated by delimiter.
func (ns *Namespace) Split(a any, delimiter string) ([]string, error) {
	aStr, err := cast.ToStringE(a)
	if err != nil {

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Clamp or check the string length first: `{{ if gt (len $s) 3 }}{{ slicestr $s 0 3 }}{{ end }}`
  2. Use `substr` if you need negative/from-the-end indexing: `{{ substr $s -3 }}`
  3. Remember indices are rune-based; derive bounds from `strings.RuneCount` semantics (Hugo's `len` on strings counts runes) rather than byte offsets

Example fix

// before
{{ slicestr .Title 0 10 }}  <!-- fails when .Title has < 10 runes -->
// after
{{ if ge (len .Title) 10 }}{{ slicestr .Title 0 10 }}{{ else }}{{ .Title }}{{ end }}
Defensive patterns

Strategy: validation

Validate before calling

{{ $s := .s }}{{ $start := int .start }}{{ $end := int .end }}
{{ $n := len $s }}
{{ $start = math.Max (math.Min $start $n) (sub 0 $n) }}
{{ $end = math.Min $end $n }}
{{ if lt $start $end }}{{ substr $s $start $end }}{{ end }}

Try / catch

{{ with try (substr .s .start .end) }}{{ if .Err }}{{ warnf "substr out of range for %q" .s }}{{ else }}{{ .Value }}{{ end }}{{ end }}

Prevention

When it happens

Trigger: `{{ slicestr "abc" 5 }}` (start ≥ rune length), `{{ slicestr "abc" -1 }}` (negative start), `{{ slicestr "abc" 0 10 }}` (end > rune length), or start on an empty string.

Common situations: 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).

Related errors


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