go-gorm/gorm · error

invalid field found for struct %v's field %s: define a valid

Error message

invalid field found for struct %v's field %s: define a valid foreign key for relations or implement the Valuer/Scanner interface

What it means

GORM's relation guesser walks a fallback chain (guess → belongs → embedded-belongs → has → embedded-has). If every strategy fails to find usable foreign keys, the final default branch reports that the field is invalid: either define an explicit foreign key or make the type implement driver.Valuer/sql.Scanner. It means the field looks like a relation but no convention-based key could be matched.

Source

Thrown at schema/relationship.go:483

			gl = guessBelongs
		} else {
			gl = guessHas
		}
	}

	reguessOrErr := func() {
		switch cgl {
		case guessGuess:
			schema.guessRelation(relation, field, guessBelongs)
		case guessBelongs:
			schema.guessRelation(relation, field, guessEmbeddedBelongs)
		case guessEmbeddedBelongs:
			schema.guessRelation(relation, field, guessHas)
		case guessHas:
			schema.guessRelation(relation, field, guessEmbeddedHas)
		// case guessEmbeddedHas:
		default:
			schema.err = fmt.Errorf("invalid field found for struct %v's field %s: define a valid foreign key for relations or implement the Valuer/Scanner interface",
				schema, field.Name)
		}
	}

	switch gl {
	case guessBelongs:
		primarySchema, foreignSchema = relation.FieldSchema, schema
	case guessEmbeddedBelongs:
		if field.OwnerSchema == nil {
			reguessOrErr()
			return
		}
		primarySchema, foreignSchema = relation.FieldSchema, field.OwnerSchema
	case guessHas:
	case guessEmbeddedHas:
		if field.OwnerSchema == nil {
			reguessOrErr()
			return

View on GitHub (pinned to 1d6ce99528)

Solutions

  1. Add an explicit `gorm:"foreignKey:..."` (and references if needed) naming real columns.
  2. If the struct is data, implement driver.Valuer and sql.Scanner on it.
  3. Or serialize it: `gorm:"serializer:json"` / `serializer:gob`.
  4. Or embed with `gorm:"embedded"` to flatten its fields into the parent table instead of a relation.

Example fix

// before
type Order struct {
    ID    uint
    Money Money // struct, no matching FK convention
}

// after
type Order struct {
    ID    uint
    Money Money `gorm:"serializer:json"`
}
Defensive patterns

Strategy: type-guard

Validate before calling

// Detect fields that look like relations but have no key convention match
func checkModel(t reflect.Type) error {
    for t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice { t = t.Elem() }
    if t.Kind() != reflect.Struct { return nil }
    for i := 0; i < t.NumField(); i++ {
        f := t.Field(i)
        ft := f.Type
        for ft.Kind() == reflect.Ptr || ft.Kind() == reflect.Slice { ft = ft.Elem() }
        if ft.Kind() == reflect.Struct && !isTimeOrSQL(ft) {
            if !strings.Contains(f.Tag.Get("gorm"), "foreignKey") &&
               !strings.Contains(f.Tag.Get("gorm"), "serializer") &&
               !strings.Contains(f.Tag.Get("gorm"), "embedded") {
                return fmt.Errorf("field %s needs foreignKey/serializer/embedded/- tag", f.Name)
            }
        }
    }
    return nil
}

Type guard

func implementsValuerScanner(t reflect.Type) bool {
    v := reflect.New(t).Elem().Interface()
    _, okV := v.(driver.Valuer)
    _, okS := any(&[1]driver.Valuer{}).(*sql.Scanner) // placeholder; real check below
    return okV || okS
}

Try / catch

if err := db.AutoMigrate(&Order{}); err != nil {
    if strings.Contains(err.Error(), "define a valid foreign key") {
        return fmt.Errorf("add foreignKey tag, serializer, or Valuer/Scanner to the offending field: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: A struct-typed field whose type has no ID-style fields matching any guessing convention and no explicit foreignKey tag; embedded structs without owner schema context (field.OwnerSchema == nil in the embedded branches); custom struct types that are data, not relations, yet lack Valuer/Scanner.

Common situations: Value objects (Money, Address) stored inline without serializers; renaming ID fields so conventions like <Owner>Name no longer match; embedding third-party structs GORM cannot introspect.

Related errors


AI-assisted analysis of go-gorm/gorm@1d6ce99528 (2026-08-15). Data as JSON: /api/errors/e897fe26baf52fb9. Report an issue: GitHub.