go-gorm/gorm · error

invalid foreign key: %s

Error message

invalid foreign key: %s

What it means

While building a many-to-many relation, GORM validates explicit `foreignKey` tag values against the owner schema via LookUpField. Each named column must exist on the declaring struct; the first unknown name aborts with 'invalid foreign key: %s'. This is the own-side (owner) validation; the references side has its own check.

Source

Thrown at schema/relationship.go:293

		err             error
		joinTableFields []reflect.StructField
		fieldsMap       = map[string]*Field{}
		ownFieldsMap    = map[string]*Field{} // fix self join many2many
		referFieldsMap  = map[string]*Field{}
		joinForeignKeys = toColumns(field.TagSettings["JOINFOREIGNKEY"])
		joinReferences  = toColumns(field.TagSettings["JOINREFERENCES"])
	)

	ownForeignFields := schema.PrimaryFields
	refForeignFields := relation.FieldSchema.PrimaryFields

	if len(relation.foreignKeys) > 0 {
		ownForeignFields = []*Field{}
		for _, foreignKey := range relation.foreignKeys {
			if field := schema.LookUpField(foreignKey); field != nil {
				ownForeignFields = append(ownForeignFields, field)
			} else {
				schema.err = fmt.Errorf("invalid foreign key: %s", foreignKey)
				return
			}
		}
	}

	if len(relation.primaryKeys) > 0 {
		refForeignFields = []*Field{}
		for _, foreignKey := range relation.primaryKeys {
			if field := relation.FieldSchema.LookUpField(foreignKey); field != nil {
				refForeignFields = append(refForeignFields, field)
			} else {
				schema.err = fmt.Errorf("invalid foreign key: %s", foreignKey)
				return
			}
		}
	}

	for idx, ownField := range ownForeignFields {

View on GitHub (pinned to 1d6ce99528)

Solutions

  1. Correct the foreignKeys value to a field that exists on the declaring struct.
  2. Drop the foreignKeys tag to let GORM default to the schema's primary fields.
  3. If the key is embedded, use the exported field name reachable by LookUpField.

Example fix

// before
type Tag struct {
    ID      uint
    Members []User `gorm:"many2many:user_tags;foreignKey:TagUID"`
}

// after
type Tag struct {
    ID      uint
    TagUID  string
    Members []User `gorm:"many2many:user_tags;foreignKey:TagUID"`
}
Defensive patterns

Strategy: validation

Validate before calling

// Validate many2many foreignKeys resolve on the declaring struct
func fieldExists(model any, name string) bool {
    t := reflect.TypeOf(model)
    for t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice { t = t.Elem() }
    _, ok := t.FieldByName(name)
    return ok
}
if !fieldExists(&Tag{}, "TagUID") { panic("foreignKey TagUID missing on Tag") }

Try / catch

if err := db.AutoMigrate(&Tag{}); err != nil {
    if strings.Contains(err.Error(), "invalid foreign key") {
        return fmt.Errorf("foreignKeys tag must name a field on the declaring model: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: `Members []User `gorm:"many2many:user_tags;foreignKey:UserRef"`` where the declaring struct has no field resolving to UserRef. Also triggered by case mismatches or names that only exist on the join table, not on the model.

Common situations: Renaming model fields during refactor without updating relation tags; pointing foreignKeys at join-table columns; typos or stale names after merging models.

Related errors


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