{"record":{"id":"6e947910045a246c","repo":"gohugoio/hugo","slug":"s-is-an-unexported-field-of-struct-type-s","errorCode":null,"errorMessage":"%s is an unexported field of struct type %s","messagePattern":"(.+?) is an unexported field of struct type (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tpl/collections/where.go","lineNumber":372,"sourceCode":"\t\tif len(res) == 2 && !res[1].IsNil() {\n\t\t\treturn zero, fmt.Errorf(\"error at calling a method %s of type %s: %s\", elemName, typ, res[1].Interface().(error))\n\t\t}\n\t\treturn res[0], nil\n\t}\n\n\t// elemName isn't a method so next start to check whether it is\n\t// a struct field or a map value. In both cases, it mustn't be\n\t// a nil value\n\tif isNil {\n\t\treturn zero, fmt.Errorf(\"can't evaluate a nil pointer of type %s by a struct field or map key name %s\", typ, elemName)\n\t}\n\tobj = reflect.Indirect(obj)\n\tswitch obj.Kind() {\n\tcase reflect.Struct:\n\t\tft, ok := obj.Type().FieldByName(elemName)\n\t\tif ok {\n\t\t\tif ft.PkgPath != \"\" && !ft.Anonymous {\n\t\t\t\treturn zero, fmt.Errorf(\"%s is an unexported field of struct type %s\", elemName, typ)\n\t\t\t}\n\t\t\treturn obj.FieldByIndex(ft.Index), nil\n\t\t}\n\t\treturn zero, fmt.Errorf(\"%s isn't a field of struct type %s\", elemName, typ)\n\tcase reflect.Map:\n\t\tkv := reflect.ValueOf(elemName)\n\t\tif kv.Type().AssignableTo(obj.Type().Key()) {\n\t\t\treturn obj.MapIndex(kv), nil\n\t\t}\n\t\treturn zero, fmt.Errorf(\"%s isn't a key of map type %s\", elemName, typ)\n\t}\n\treturn zero, fmt.Errorf(\"%s is neither a struct field, a method nor a map element of type %s\", elemName, typ)\n}\n\n// parseWhereArgs parses the end arguments to the where function.  Return a\n// match value and an operator, if one is defined.\nfunc parseWhereArgs(args ...any) (mv reflect.Value, op string, err error) {\n\tswitch len(args) {","sourceCodeStart":354,"sourceCodeEnd":390,"githubUrl":"https://github.com/gohugoio/hugo/blob/52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2/tpl/collections/where.go#L354-L390","documentation":"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.","triggerScenarios":"`{{ where $pages \"draft\" \"==\" true }}` against a struct whose field is `draft bool` (lowercase) rather than the exported `Draft bool`.","commonSituations":"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.","solutions":["Use the exported field name (capitalized first letter) as the where key.","If the field is genuinely private, expose it via an exported field or a method returning a value.","Check the type definition for the exact exported field name."],"exampleFix":"// before\n{{ where $pages \"draft\" \"==\" true }}\n// after\n{{ where $pages \"Draft\" \"==\" true }}","handlingStrategy":"validation","validationCode":"// Go-side: confirm the field is exported and present before templating\nft, ok := reflect.TypeOf(item).FieldByName(fieldName)\nok = ok && ft.PkgPath == \"\" || ft.Anonymous","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always capitalize the first letter of fields used in templates.","Keep a documented list of exported fields per model.","Add a Go test asserting template-referenced fields are exported."],"tags":["hugo","go-templates","where","reflection","unexported","struct"],"backgroundTag":null,"analyzedSha":"52c9bd7908b4d02d4d0ff8f82a888834d6ee10d2","analyzedAt":"2026-08-09T21:49:36.660Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}