{"record":{"id":"a9797e1a4edc8775","repo":"jmoiron/sqlx","slug":"expected-a-struct-but-struct-s-has-no-exported-f","errorCode":null,"errorMessage":"expected a struct, but struct %s has no exported fields","messagePattern":"expected a struct, but struct (.+?) has no exported fields","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"sqlx.go","lineNumber":880,"sourceCode":"\tClose() error\n\tColumns() ([]string, error)\n\tErr() error\n\tNext() bool\n\tScan(...interface{}) error\n}\n\n// structOnlyError returns an error appropriate for type when a non-scannable\n// struct is expected but something else is given\nfunc structOnlyError(t reflect.Type) error {\n\tisStruct := t.Kind() == reflect.Struct\n\tisScanner := reflect.PtrTo(t).Implements(_scannerInterface)\n\tif !isStruct {\n\t\treturn fmt.Errorf(\"expected %s but got %s\", reflect.Struct, t.Kind())\n\t}\n\tif isScanner {\n\t\treturn fmt.Errorf(\"structscan expects a struct dest but the provided struct type %s implements scanner\", t.Name())\n\t}\n\treturn fmt.Errorf(\"expected a struct, but struct %s has no exported fields\", t.Name())\n}\n\n// scanAll scans all rows into a destination, which must be a slice of any\n// type.  It resets the slice length to zero before appending each element to\n// the slice.  If the destination slice type is a Struct, then StructScan will\n// be used on each row.  If the destination is some other kind of base type,\n// then each row must only have one column which can scan into that type.  This\n// allows you to do something like:\n//\n//\trows, _ := db.Query(\"select id from people;\")\n//\tvar ids []int\n//\tscanAll(rows, &ids, false)\n//\n// and ids will be a list of the id results.  I realize that this is a desirable\n// interface to expose to users, but for now it will only be exposed via changes\n// to `Get` and `Select`.  The reason that this has been implemented like this is\n// this is the only way to not duplicate reflect work in the new API while\n// maintaining backwards compatibility.","sourceCodeStart":862,"sourceCodeEnd":898,"githubUrl":"https://github.com/jmoiron/sqlx/blob/41dac167fdad5e3fd81d66cafba0951dc6823a30/sqlx.go#L862-L898","documentation":"structOnlyError fires this when the destination is a struct kind but has no exported fields, so the mapper has nothing to bind columns to. Unexported fields are invisible to reflection-based mapping, making StructScan impossible.","triggerScenarios":"Calling StructScan/Get/Select with a struct destination where every field is unexported (lowercase) or the struct is empty.","commonSituations":"Defining model structs with all-lowercase fields; internal/config structs reused as scan destinations; generating structs with private fields.","solutions":["Export the struct fields you want scanned (capitalize them) and add db tags.","Use a different struct with exported fields as the scan destination.","Add at least one exported field if the struct is intentionally blank."],"exampleFix":"// before\ntype user struct { name string }\ndb.Get(&u, \"SELECT name FROM users\")\n\n// after\ntype User struct { Name string `db:\"name\"` }\ndb.Get(&u, \"SELECT name FROM users\")","handlingStrategy":"type-guard","validationCode":"t := reflect.TypeOf(dest).Elem()\nif t.NumField() == 0 || !hasExportedField(t) {\n    return errors.New(\"struct dest has no exported fields\")\n}","typeGuard":"func hasExportedFields(dest interface{}) bool {\n    t := reflect.TypeOf(dest)\n    if t == nil || t.Kind() != reflect.Ptr || t.Elem().Kind() != reflect.Struct {\n        return false\n    }\n    for i := 0; i < t.Elem().NumField(); i++ {\n        if t.Elem().Field(i).PkgPath == \"\" { return true }\n    }\n    return false\n}","tryCatchPattern":"if err := rows.StructScan(&s); err != nil {\n    if strings.Contains(err.Error(), \"no exported fields\") {\n        return fmt.Errorf(\"model %T must export scanned fields\", s)\n    }\n    return err\n}","preventionTips":["Export fields on any struct used as a scan destination.","Use separate DTOs with exported fields for DB rows.","Lint for lowercase-only model structs."],"tags":["sqlx","struct-scan","exported-fields","reflection"],"backgroundTag":"scan-dest-type-mismatch","analyzedSha":"41dac167fdad5e3fd81d66cafba0951dc6823a30","analyzedAt":"2026-09-03T06:10:20.382Z","contentChangedAt":"2026-09-03T06:10:20.382Z","schemaVersion":2},"datasetVersion":"2026-09-10T12:17:11.382Z"}