{"id":"5fd1561695aef9d5","repo":"gohugoio/hugo","slug":"expected-a-slice-got-t","errorCode":null,"errorMessage":"expected a slice, got %T","messagePattern":"expected a slice, got %T","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"common/collections/append.go","lineNumber":44,"sourceCode":"func Append(to any, from ...any) (any, error) {\n\tif len(from) == 0 {\n\t\treturn to, nil\n\t}\n\ttov, toIsNil := hreflect.Indirect(reflect.ValueOf(to))\n\n\ttoIsNil = toIsNil || to == nil\n\tvar tot reflect.Type\n\n\tif !toIsNil {\n\t\tif tov.Kind() == reflect.Slice {\n\t\t\t// Create a copy of tov, so we don't modify the original.\n\t\t\tc := reflect.MakeSlice(tov.Type(), tov.Len(), tov.Len()+len(from))\n\t\t\treflect.Copy(c, tov)\n\t\t\ttov = c\n\t\t}\n\n\t\tif tov.Kind() != reflect.Slice {\n\t\t\treturn nil, fmt.Errorf(\"expected a slice, got %T\", to)\n\t\t}\n\n\t\ttot = tov.Type().Elem()\n\t\tif tot.Kind() == reflect.Slice {\n\t\t\ttotvt := tot.Elem()\n\t\t\tfromvs := make([]reflect.Value, len(from))\n\t\t\tfor i, f := range from {\n\t\t\t\tfromv := reflect.ValueOf(f)\n\t\t\t\tfromt := fromv.Type()\n\t\t\t\tif fromt.Kind() == reflect.Slice {\n\t\t\t\t\tfromt = fromt.Elem()\n\t\t\t\t}\n\t\t\t\tif totvt != fromt {\n\t\t\t\t\treturn nil, fmt.Errorf(\"cannot append slice of %s to slice of %s\", fromt, totvt)\n\t\t\t\t} else {\n\t\t\t\t\tfromvs[i] = fromv\n\t\t\t\t}\n\t\t\t}","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/gohugoio/hugo/blob/8a468df065a75c1c7cf9f6850f32148746590ea5/common/collections/append.go#L26-L62","documentation":"`collections.Append` implements the template `append` function. After dereferencing the first argument, if it's non-nil but not a slice (a string, map, number, page, etc.), appending is impossible and this error names the concrete type. Only slices (or nil, which starts a new slice) are valid append targets.","triggerScenarios":"Template code calling `append` with a non-slice first collection: `{{ append \"a\" $x }}`, `{{ append .Params.title $x }}`, or appending onto a value produced by `dict`. Note the argument order — in Hugo, `append ELEMENT COLLECTION` (collection last) also flows through this when the collection position holds a non-slice.","commonSituations":"Confusing Hugo's `append` argument order with Go's, so a scalar ends up in the slice position; a `.Params` field expected to be a list but authored as a string in front matter; appending to a `.Scratch` value that was set to a scalar earlier.","solutions":["Ensure the collection argument (the last one in Hugo's `append`) is a slice — initialize with `$s := slice`, not a string.","If appending to a page param, fix the front matter so the field is a list (`items: [a]` not `items: a`).","When accumulating in templates, start with `$acc := slice` and reassign: `$acc = $acc | append $item`."],"exampleFix":"{{/* before */}}\n{{ $s := \"\" }}\n{{ $s = append \"x\" $s }}\n\n{{/* after */}}\n{{ $s := slice }}\n{{ $s = append \"x\" $s }}","handlingStrategy":"type-guard","validationCode":"rv := reflect.ValueOf(to)\nif rv.Kind() != reflect.Slice && rv.Kind() != reflect.Array {\n    return fmt.Errorf(\"append target must be a slice, got %T\", to)\n}","typeGuard":"func isSlice(v any) bool {\n    k := reflect.TypeOf(v).Kind()\n    return k == reflect.Slice || k == reflect.Array\n}","tryCatchPattern":"{{ $s := $x }}\n{{ if not (reflect.IsSlice $s) }}{{ $s = slice $s }}{{ end }}\n{{ $s = $s | append $item }}","preventionTips":["Initialize accumulators with `slice` (e.g. `{{ $items := slice }}`) before using `append` in templates","Never append onto a scalar or map — the first argument set must be a slice","Use `reflect.IsSlice` in templates to guard values of uncertain shape (e.g. params that may be scalar or list)"],"tags":["hugo","templates","collections","type-conversion"],"analyzedSha":"8a468df065a75c1c7cf9f6850f32148746590ea5","analyzedAt":"2026-07-31T21:23:07.045Z","schemaVersion":2}