gohugoio/hugo · error

elements must be comparable

Error message

elements must be comparable

What it means

Set-like collection functions in Hugo (union, intersect, symdiff, uniq — anything that builds an identity set via `collectIdentities`) must place each element in a Go map key. That requires the element's type to be comparable per Go's rules; slices, maps, and funcs (or structs containing them) are not, so Hugo rejects the operation.

Source

Thrown at tpl/collections/reflect_helpers.go:67

		return ip.TransientKey()
	}

	return vv
}

// collects identities from the slices in seqs into a set. Numeric values are normalized,
// pointers unwrapped.
func collectIdentities(seqs ...any) (map[any]bool, error) {
	seen := make(map[any]bool)
	for _, seq := range seqs {
		v := reflect.ValueOf(seq)
		switch v.Kind() {
		case reflect.Array, reflect.Slice:
			for i := range v.Len() {
				ev, _ := hreflect.Indirect(v.Index(i))

				if !ev.Type().Comparable() {
					return nil, errors.New("elements must be comparable")
				}

				seen[normalize(ev)] = true
			}
		default:
			return nil, fmt.Errorf("arguments must be slices or arrays")
		}
	}

	return seen, nil
}

// We have some different numeric and string types that we try to behave like
// they were the same.
func convertValue(v reflect.Value, to reflect.Type) (reflect.Value, error) {
	if v.Type().AssignableTo(to) {
		return v, nil
	}

View on GitHub ↗ (pinned to 8a468df065)

Solutions

  1. Operate on a comparable key extracted from each element instead of the whole element — e.g. use `where`, or build slices of strings/ids first (`{{ $ids := slice }}{{ range $items }}{{ $ids = $ids | append .id }}{{ end }}`), then apply uniq/intersect to those.
  2. For Pages collections, use the page-aware set functions on the Pages themselves (Pages are comparable via identity), not on derived maps.
  3. Restructure data files so the compared values are scalars (strings/numbers) rather than nested arrays/maps.

Example fix

<!-- before -->
{{ $u := uniq (slice (dict "id" 1) (dict "id" 1)) }}
<!-- after: dedupe by scalar key -->
{{ $ids := slice }}{{ range $items }}{{ $ids = $ids | append .id }}{{ end }}
{{ $u := uniq $ids }}
Defensive patterns

Strategy: type-guard

Validate before calling

{{ $s := slice 1 2 3 }}
{{ $comparable := true }}
{{ range $s }}{{ if or (reflect.IsMap .) (reflect.IsSlice .) }}{{ $comparable = false }}{{ end }}{{ end }}
{{ if $comparable }}{{ uniq $s }}{{ end }}

Type guard

{{ $isComparable := not (or (reflect.IsMap $el) (reflect.IsSlice $el)) }}

Try / catch

{{ with try (uniq $s) }}{{ with .Err }}{{ warnf "uniq needs comparable elements: %s" . }}{{ else }}{{ .Value }}{{ end }}{{ end }}

Prevention

When it happens

Trigger: Calling `union`, `intersect`, `symdiff`, or `uniq` on collections whose elements are themselves slices or maps — e.g. `{{ symdiff (slice (slice 1 2)) (slice (slice 3)) }}` or deduplicating a slice of dicts/param maps.

Common situations: Trying to `uniq`/`intersect` slices of `dict` values or front-matter param maps; nested data from data files (arrays of arrays from JSON/YAML); comparing lists of taxonomy term maps instead of the term strings.

Related errors


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