gohugoio/hugo · error

isset unable to use key of type %T as index

Error message

isset unable to use key of type %T as index

What it means

Thrown by Hugo's `isset` template function (Namespace.IsSet) when the collection argument is an array, channel, or slice but the supplied key cannot be converted to an integer index via cast.ToIntE. Arrays/slices/channels are positional, so a numeric index is mandatory; any other key type (string, struct, map, bool) is meaningless as a positional lookup and is rejected. The %T verb fills in the actual Go type of the offending key.

Solutions

  1. Pass an integer literal or integer-typed value as the key, e.g. `{{ isset $slice 0 }}`.
  2. If you meant string-key lookup, ensure the first argument is a map (`map[string]...`), not a slice/array.
  3. If the key is dynamic, coerce it to int before the call: `{{ $k := int $key }}{{ isset $slice $k }}`.
  4. Guard the call with a type check or restructure data so the same collection is always addressed by index.

Example fix

// before
{{ if isset .Pages "title" }}...{{ end }}
// after
{{ if isset .Pages 0 }}...{{ end }}
// or, if the collection is actually a map:
{{ if isset .Params "title" }}...{{ end }}
Defensive patterns

Strategy: validation

Validate before calling

// Validate key is int before IsSet on an array/slice/chan.
func isSetIntKey(c, key any) bool {
    v := reflect.ValueOf(c)
    if k := v.Kind(); k == reflect.Array || k == reflect.Slice || k == reflect.Chan {
        if n, err := cast.ToIntE(key); err == nil {
            return v.Len() > n
        }
        return false // would trigger the error; bail out
    }
    // fall through to map handling
    return false
}

Type guard

func isIndexableByKey(c any) bool {
    v := reflect.ValueOf(c)
    switch v.Kind() {
    case reflect.Array, reflect.Slice, reflect.Chan:
        return true
    case reflect.Map:
        return true
    }
    return false
}

Prevention

When it happens

Trigger: Calling `{{ isset $slice "foo" }}`, `{{ isset .Pages "title" }}`, or `{{ isset $array $someStruct }}` — i.e. passing a non-integer second argument where the first is an array/slice/chan. Also triggers when the key is a float like 1.5 (cast.ToIntE rejects it) or a numeric string that cast cannot coerce.

Common situations: Template author confuses a slice with a map (e.g. treats `.Pages` as key-addressable by string), passes front-matter string keys to a positional collection, or flows an `interface{}` whose dynamic type is a string into `isset` against a slice. Common during partial refactors where a map was replaced by a slice but template calls were not updated.

Related errors


AI-assisted analysis of gohugoio/hugo@52c9bd7908 (2026-08-09). Data as JSON: /api/errors/631a3770cdeefea2. Report an issue: GitHub.

Appendix: source

Thrown at tpl/collections/collections.go:340

	if g, ok := in.(collections.Grouper); ok {
		return g.Group(key, items)
	}

	return nil, fmt.Errorf("grouping not supported for type %T %T", items, in)
}

// IsSet returns whether a given array, channel, slice, or map in c has the given key
// defined.
func (ns *Namespace) IsSet(c any, key any) (bool, error) {
	av := reflect.ValueOf(c)
	kv := reflect.ValueOf(key)

	switch av.Kind() {
	case reflect.Array, reflect.Chan, reflect.Slice:
		k, err := cast.ToIntE(key)
		if err != nil {
			return false, fmt.Errorf("isset unable to use key of type %T as index", key)
		}
		if av.Len() > k {
			return true, nil
		}
	case reflect.Map:
		if kv.Type() == av.Type().Key() {
			return av.MapIndex(kv).IsValid(), nil
		}
	default:
		ns.deps.Log.Warnf("calling IsSet with unsupported type %q (%T) for key %v will always return false.\n", av.Kind(), c, key)
	}

	return false, nil
}

// Last returns the last limit items in the list l.
func (ns *Namespace) Last(limit any, l any) (any, error) {
	if limit == nil || l == nil {

View on GitHub (pinned to 52c9bd7908)