gohugoio/hugo · error
%s is an unexported method of type %s
Error message
%s is an unexported method of type %s
What it means
evaluateSubElem found a method matching the key name on the element's type, but the method has a non-empty PkgPath, meaning it is unexported (lowercase). Go reflection cannot call unexported methods, so Hugo rejects the key rather than attempting a call that would panic.
Source
Thrown at tpl/collections/where.go:341
objPtr := obj
if !hreflect.IsInterfaceOrPointer(objPtr.Kind()) && objPtr.CanAddr() {
objPtr = objPtr.Addr()
}
mt := hreflect.GetMethodByNameForType(objPtr.Type(), elemName)
if mt.Func.IsValid() {
// Receiver is the first argument.
args := []reflect.Value{objPtr}
num := mt.Type.NumIn()
maxNumIn := 1
if num > 1 && hreflect.IsContextType(mt.Type.In(1)) {
args = append(args, ctx)
maxNumIn = 2
}
switch {
case mt.PkgPath != "":
return zero, fmt.Errorf("%s is an unexported method of type %s", elemName, typ)
case mt.Type.NumIn() > maxNumIn:
return zero, fmt.Errorf("%s is a method of type %s but requires more than %d parameter", elemName, typ, maxNumIn)
case mt.Type.NumOut() == 0:
return zero, fmt.Errorf("%s is a method of type %s but returns no output", elemName, typ)
case mt.Type.NumOut() > 2:
return zero, fmt.Errorf("%s is a method of type %s but returns more than 2 outputs", elemName, typ)
case mt.Type.NumOut() == 1 && mt.Type.Out(0).Implements(errorType):
return zero, fmt.Errorf("%s is a method of type %s but only returns an error type", elemName, typ)
case mt.Type.NumOut() == 2 && !mt.Type.Out(1).Implements(errorType):
return zero, fmt.Errorf("%s is a method of type %s returning two values but the second value is not an error type", elemName, typ)
}
res := mt.Func.Call(args)
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
}
View on GitHub ↗ (pinned to 8a468df065)
Solutions
- Use the exported (capitalized) method or field name, e.g. `"Title"` not `"title"`.
- Check Hugo's Page/Resource docs for the correct exported accessor name.
- If the data is a map, ensure the map key casing matches exactly instead of relying on a method.
Example fix
<!-- before -->
{{ range where .Site.RegularPages "kind" "section" }}
<!-- after -->
{{ range where .Site.RegularPages "Kind" "section" }} Defensive patterns
Strategy: validation
Validate before calling
// Go-side: export the method before exposing the type to templates
// reflect check: m, ok := reflect.TypeOf(v).MethodByName("Name"); ok && m.IsExported() Prevention
- Only reference exported (capitalized) methods in where key paths
- When passing custom Go types into Hugo templates, export any method templates need
- Remember key lookup falls back to methods; a lowercase name will never resolve
When it happens
Trigger: `where $collection "someMethod" ...` where the element type (often a custom struct passed via a module or theme's Go code, or a Hugo internal type) has a lowercase method of that exact name; casing mistakes like using `permalink` when the type only exposes it unexported.
Common situations: Template authors guessing at method names with lowercase spelling; copying JSON-style lowercase field names into `where` keys when the underlying Go type uses exported CamelCase methods (`Title`, `Permalink`).
Related errors
- can't evaluate an invalid value
- can't iterate over %T
- invalid intersect values
- no such operator
- %s isn't a field of struct type %s
AI-assisted analysis of gohugoio/hugo@8a468df065 (2026-07-31).
Data as JSON: /data/errors/4607a68461f34cf3.json.
Report an issue: GitHub ↗.