Creating Custom GORM Hooks

GORM hooks are methods that run automatically at defined points in a record's lifecycle - before a create, after an update, and so on. Used well they centralize logic you would otherwise scatter and forget; used carelessly they hide behavior that future readers cannot find.

The available hooks

GORM looks for specially named methods on your model and calls them around the relevant operation. The most useful are BeforeCreate, AfterCreate, BeforeUpdate, and BeforeDelete. Each receives the active transaction and can return an error to abort the whole operation.

A practical example

Generating a UUID primary key before insert is the textbook case - it guarantees every row gets an ID without relying on every call site to remember:

func (u *User) BeforeCreate(tx *gorm.DB) error {
    if u.ID == uuid.Nil {
        u.ID = uuid.New()
    }
    if u.Email == "" {
        return errors.New("email is required")
    }
    return nil
}

Because the hook runs inside the transaction, returning an error rolls the create back cleanly - you never end up with a half-written record.

Where hooks earn their place

  • Normalizing data, such as lower-casing an email before it is stored.
  • Setting derived fields, like a slug computed from a title.
  • Enforcing an invariant that must hold for every write, regardless of which code path triggered it.

Where they bite back

Hooks are invisible at the call site. A teammate reading db.Create(&user) has no hint that a UUID is being minted or validation is running. Keep them small, keep them predictable, and never put a network call or a side effect across systems inside one - a failed email send should not roll back a database write. When in doubt, prefer an explicit service method over a clever hook.

  • #go
  • #gorm
  • #hooks
  • #database