gohugoio/hugo · error
can't index item of type %s
Error message
can't index item of type %s
What it means
Raised by the `index` template action (e.g. `{{index .X 0}}`) when the target value's reflect.Kind is not Array, Slice, String, or Map. The index builtin only walks container types; anything else (struct, int, bool, func, chan) falls into the default branch at funcs.go:230 and is rejected. The %s is the offending value's Go type.
Source
Thrown at tpl/internal/go_templates/texttemplate/funcs.go:230
if err != nil {
return reflect.Value{}, err
}
item = item.Index(x)
case reflect.Map:
index, err := prepareArg(index, item.Type().Key())
if err != nil {
return reflect.Value{}, err
}
if x := item.MapIndex(index); x.IsValid() {
item = x
} else {
item = reflect.Zero(item.Type().Elem())
}
case reflect.Invalid:
// the loop holds invariant: item.IsValid()
panic("unreachable")
default:
return reflect.Value{}, fmt.Errorf("can't index item of type %s", item.Type())
}
}
return item, nil
}
// Slicing.
// slice returns the result of slicing its first argument by the remaining
// arguments. Thus "slice x 1 2" is, in Go syntax, x[1:2], while "slice x"
// is x[:], "slice x 1" is x[1:], and "slice x 1 2 3" is x[1:2:3]. The first
// argument must be a string, slice, or array.
func slice(item reflect.Value, indexes ...reflect.Value) (reflect.Value, error) {
item = indirectInterface(item)
if !item.IsValid() {
return reflect.Value{}, fmt.Errorf("slice of untyped nil")
}
var isNil bool
if item, isNil = indirect(item); isNil {View on GitHub (pinned to 52c9bd7908)
Solutions
- Inspect the actual runtime type of the value with `{{printf "%T" .Foo}}` and compare against what the template assumes.
- Switch the template from `index` to field/method access (`{{.Foo.Bar}}`) if the value is a struct.
- Change the data passed in (the Go context or front matter) to a slice/array/map so indexing is valid.
- Guard with `{{if reflect.IsSlice .Foo}}...{{end}}` or a custom helper before indexing.
Example fix
// before
{{index .Tags 0}} // .Tags is a string, not []string
// after
{{.Tags}} // string is used directly
// or pass []string from Go: {{index .Tags 0}} Defensive patterns
Strategy: type-guard
Validate before calling
// In Go, before rendering, ensure container types:
// {{printf "%T" .Foo}} in template to inspect
// In template, guard before indexing:
// {{if reflect.IsSlice .Foo}}{{index .Foo 0}}{{end}} Type guard
// Expose a helper in FuncMap:
func isIndexable(v interface{}) bool {
if v == nil { return false }
switch reflect.TypeOf(v).Kind() {
case reflect.Slice, reflect.Array, reflect.Map, reflect.String:
return true
}
return false
}
// template: {{if isIndexable .Foo}}{{index .Foo 0}}{{end}} Prevention
- Pass only container types (slice/array/map/string) into templates you intend to index.
- Print the runtime type with {{printf "%T" .X}} when authoring index actions.
- Keep Go struct fields stable: avoid flipping a slice field to a struct without auditing templates.
When it happens
Trigger: `{{index .Foo 0}}` where `.Foo` is a struct, int, bool, pointer-to-struct (after indirect it derefs to struct), time.Time, or any non-container. Also `{{index . 5}}` on a scalar root, or chaining `{{index (index .Matrix 0) 0}}` when an inner step returns a struct.
Common situations: Front matter field declared as a string but template treats it as a list; a Hugo shortcode param resolved to a single value instead of the expected slice; paginating a non-slice; refactoring a Go struct field from `[]string` to a custom struct type without updating templates.
Related errors
- can't slice item of type %s
- len of type %s
- non-function %s of type %s
- non-comparable types %s: %v, %s: %v
- argument must be a slice
AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09).
Data as JSON: /api/errors/e4f1261a405cd3d1.
Report an issue: GitHub.