gohugoio/hugo · error

%s is an unexported field of struct type %s

Error message

%s is an unexported field of struct type %s

What it means

In the struct branch of `evaluateSubElem` (where.go:368-376), after `FieldByName` succeeds Hugo checks `ft.PkgPath != "" && !ft.Anonymous`. A non-empty PkgPath means the field is unexported (lowercase); reflect cannot read it, so this error names the field and struct type.

Source

Thrown at tpl/collections/where.go:372

		if len(res) == 2 && !res[1].IsNil() {
			return zero, fmt.Errorf("error at calling a method %s of type %s: %s", elemName, typ, res[1].Interface().(error))
		}
		return res[0], nil
	}

	// elemName isn't a method so next start to check whether it is
	// a struct field or a map value. In both cases, it mustn't be
	// a nil value
	if isNil {
		return zero, fmt.Errorf("can't evaluate a nil pointer of type %s by a struct field or map key name %s", typ, elemName)
	}
	obj = reflect.Indirect(obj)
	switch obj.Kind() {
	case reflect.Struct:
		ft, ok := obj.Type().FieldByName(elemName)
		if ok {
			if ft.PkgPath != "" && !ft.Anonymous {
				return zero, fmt.Errorf("%s is an unexported field of struct type %s", elemName, typ)
			}
			return obj.FieldByIndex(ft.Index), nil
		}
		return zero, fmt.Errorf("%s isn't a field of struct type %s", elemName, typ)
	case reflect.Map:
		kv := reflect.ValueOf(elemName)
		if kv.Type().AssignableTo(obj.Type().Key()) {
			return obj.MapIndex(kv), nil
		}
		return zero, fmt.Errorf("%s isn't a key of map type %s", elemName, typ)
	}
	return zero, fmt.Errorf("%s is neither a struct field, a method nor a map element of type %s", elemName, typ)
}

// parseWhereArgs parses the end arguments to the where function.  Return a
// match value and an operator, if one is defined.
func parseWhereArgs(args ...any) (mv reflect.Value, op string, err error) {
	switch len(args) {

View on GitHub (pinned to 52c9bd7908)

Solutions

  1. Use the exported field name (capitalized first letter) as the where key.
  2. If the field is genuinely private, expose it via an exported field or a method returning a value.
  3. Check the type definition for the exact exported field name.

Example fix

// before
{{ where $pages "draft" "==" true }}
// after
{{ where $pages "Draft" "==" true }}
Defensive patterns

Strategy: validation

Validate before calling

// Go-side: confirm the field is exported and present before templating
ft, ok := reflect.TypeOf(item).FieldByName(fieldName)
ok = ok && ft.PkgPath == "" || ft.Anonymous

Prevention

When it happens

Trigger: `{{ where $pages "draft" "==" true }}` against a struct whose field is `draft bool` (lowercase) rather than the exported `Draft bool`.

Common situations: Guessing a lowercase field name from the source; a Go struct with private fields that the public API exposes via accessors; confusion between internal struct fields and the JSON/template-visible ones.

Related errors


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