gohugoio/hugo · error

cannot append slice of %s to slice of %s

Error message

cannot append slice of %s to slice of %s

What it means

Hugo's collections.Append (backing the `append` template function) uses reflection to append values to a typed slice. When the target is a slice of slices (e.g. [][]string), every appended element must be a slice with the same element type; if the incoming value's element type differs from the target's, Append fails with this error at common/collections/append.go:58 rather than silently producing a mixed-type slice.

Source

Thrown at common/collections/append.go:58

			tov = c
		}

		if tov.Kind() != reflect.Slice {
			return nil, fmt.Errorf("expected a slice, got %T", to)
		}

		tot = tov.Type().Elem()
		if tot.Kind() == reflect.Slice {
			totvt := tot.Elem()
			fromvs := make([]reflect.Value, len(from))
			for i, f := range from {
				fromv := reflect.ValueOf(f)
				fromt := fromv.Type()
				if fromt.Kind() == reflect.Slice {
					fromt = fromt.Elem()
				}
				if totvt != fromt {
					return nil, fmt.Errorf("cannot append slice of %s to slice of %s", fromt, totvt)
				} else {
					fromvs[i] = fromv
				}
			}
			return reflect.Append(tov, fromvs...).Interface(), nil

		}

		toIsNil = tov.Len() == 0

		if len(from) == 1 {
			fromv := reflect.ValueOf(from[0])
			if !fromv.IsValid() {
				// from[0] is nil
				return appendToInterfaceSliceFromValues(tov, fromv)
			}
			fromt := fromv.Type()
			if fromt.Kind() == reflect.Slice {

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Make the appended slice's element type match the target, e.g. convert values with `string`/`int` before building the inner slice.
  2. Seed the outer slice as []interface{} (e.g. start with `slice` of mixed values) so any inner slice type is accepted.
  3. Check where the target slice was first created and make all rows come from the same source/type.

Example fix

{{/* before */}}
{{ $rows := slice (slice "a" "b") }}
{{ $rows = $rows | append (slice 1 2) }}  {{/* error */}}

{{/* after */}}
{{ $rows := slice (slice "a" "b") }}
{{ $rows = $rows | append (slice "1" "2") }}
Defensive patterns

Strategy: type-guard

Validate before calling

// Go caller: ensure element types match before append
if reflect.TypeOf(elem) != reflect.TypeOf(slice).Elem() { /* wrap in []interface{} or convert first */ }

Type guard

func canAppend(slice, elem any) bool {
	sv := reflect.ValueOf(slice)
	if sv.Kind() != reflect.Slice { return false }
	et := sv.Type().Elem()
	return et.Kind() == reflect.Interface || reflect.TypeOf(elem).AssignableTo(et)
}

Try / catch

res, err := collections.Append(to, from...)
if err != nil {
	// mixed element types: rebuild target as []interface{} and retry once
}

Prevention

When it happens

Trigger: Calling `{{ $s = $s | append ... }}` in a template where $s is a slice of slices (e.g. built from `slice (slice "a")`) and the appended value is a slice of a different element type, such as appending (slice 1 2) to [][]string, or appending a []interface{} to a [][]string.

Common situations: Templates that accumulate rows/groups (e.g. building a matrix of strings) where one appended row comes from a differently-typed source like .Params (which yields []interface{}) or integers mixed with strings; refactors that change the initial seed slice's type; data files decoding to different concrete types than the literal `slice` function produces.

Related errors


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