gohugoio/hugo · error
invalid dictionary key
Error message
invalid dictionary key
What it means
Keys in `dict` must be either a string or a []string (a string slice creates nested maps). Any other key type — int, bool, Page, nil, or an untyped param — fails this switch and aborts the dictionary construction.
Source
Thrown at tpl/collections/collections.go:188
switch v := values[i].(type) {
case string:
key = v
case []string:
for i := range len(v) - 1 {
key = v[i]
var m map[string]any
v, found := dict[key]
if found {
m = v.(map[string]any)
} else {
m = make(map[string]any)
dict[key] = m
}
dict = m
}
key = v[len(v)-1]
default:
return nil, errors.New("invalid dictionary key")
}
dict[key] = values[i+1]
}
return root, nil
}
// First returns the first limit items in list l.
func (ns *Namespace) First(limit any, l any) (any, error) {
if limit == nil || l == nil {
return nil, errors.New("both limit and seq must be provided")
}
limitv, err := cast.ToIntE(limit)
if err != nil {
return nil, err
}
View on GitHub ↗ (pinned to 8a468df065)
Solutions
- Convert the key to a string first: `{{ dict (string $i) $v }}` or `{{ dict (printf "%d" $i) $v }}`.
- Quote key values in front matter/config so YAML keeps them strings.
- For nested keys, ensure the slice contains only strings: `{{ dict (slice "outer" "inner") $v }}`.
Example fix
<!-- before -->
{{ $m := dict $i .Title }}
<!-- after -->
{{ $m := dict (string $i) .Title }} Defensive patterns
Strategy: type-guard
Validate before calling
{{ $k := .Params.key | default "" }}
{{ if ne (printf "%T" $k) "string" }}{{ $k = printf "%v" $k }}{{ end }}
{{ $d := dict $k "value" }} Type guard
{{/* coerce keys to strings before dict */}}
{{ $key := printf "%v" $rawKey }} Prevention
- Use only strings (or string slices for nested keys) as dict keys
- Coerce dynamic keys with `printf "%v"` or `string` before passing to dict
- Watch for front-matter values parsed as ints/bools when you expected strings (e.g. unquoted YAML)
When it happens
Trigger: `{{ dict 1 "one" }}`, `{{ dict .Params.someKey "v" }}` where the param is not a string, or `{{ dict (slice 1 2) "v" }}` (an []int slice, not []string).
Common situations: Using loop indices or integers as keys, passing front-matter params that YAML parsed as numbers/booleans (e.g. an unquoted `2024` or `true`), or expecting `slice "a" "b"` nesting to work with non-string elements.
Related errors
- arguments to symdiff must be slices or arrays
- can't iterate over %T
- invalid dictionary call
- grouping not supported for type %T %T
- symdiff: failed to convert value: %w
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/9da14e1c327badf7.json.
Report an issue: GitHub ↗.