Hooks
Hooks let you run code at specific points in a model's lifecycle. Use them for validation, data transformation, side effects, and more.
Complete reference for model lifecycle hooks.
Hook Types​
BeforeCreate​
Called before creating a new object:
BeforeCreate: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
return nil
}
AfterCreate​
Called after creating a new object:
AfterCreate: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
// Send notification, log, etc.
return nil
}
BeforeUpdate​
Called before updating an object:
BeforeUpdate: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
// Validate changes, etc.
return nil
}
AfterUpdate​
Called after updating an object:
AfterUpdate: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
// Update cache, etc.
return nil
}
BeforeSave​
Called before saving (create or update):
BeforeSave: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
return nil
}
AfterSave​
Called after saving (create or update):
AfterSave: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
return nil
}
BeforeDelete​
Called before deleting an object:
BeforeDelete: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
return nil
}
AfterDelete​
Called after deleting an object:
AfterDelete: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
// Cleanup, etc.
return nil
}
Clean​
Custom validation:
Clean: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
if user.Username == "" {
return errors.New("username is required")
}
return nil
}
Validate​
Additional validation:
Validate: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
// Additional validation
return nil
}
Hook Execution Order​
On Create​
BeforeSaveBeforeCreate- Database insert
AfterCreateAfterSave
On Update​
BeforeSaveBeforeUpdate- Database update
AfterUpdateAfterSave
On Delete​
BeforeDelete- Database delete
AfterDelete
Examples​
Password Hashing​
func (User) Hooks() *schema.ModelHooks {
return &schema.ModelHooks{
BeforeCreate: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
if user.Password != "" {
hashed, err := auth.HashPassword(user.Password)
if err != nil {
return err
}
user.Password = hashed
}
return nil
},
BeforeUpdate: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
// Only hash if password changed
if user.Password != "" && !strings.HasPrefix(user.Password, "$2a$") {
hashed, err := auth.HashPassword(user.Password)
if err != nil {
return err
}
user.Password = hashed
}
return nil
},
}
}
Timestamps​
func (Post) Hooks() *schema.ModelHooks {
return &schema.ModelHooks{
BeforeCreate: func(ctx context.Context, instance interface{}) error {
post := instance.(*Post)
now := time.Now()
post.CreatedAt = now
post.UpdatedAt = now
return nil
},
BeforeUpdate: func(ctx context.Context, instance interface{}) error {
post := instance.(*Post)
post.UpdatedAt = time.Now()
return nil
},
}
}
Validation​
func (User) Hooks() *schema.ModelHooks {
return &schema.ModelHooks{
Clean: func(ctx context.Context, instance interface{}) error {
user := instance.(*User)
if user.Username == "" {
return errors.New("username is required")
}
if !isValidEmail(user.Email) {
return errors.New("invalid email address")
}
return nil
},
}
}
Best Practices​
- Keep hooks fast - Hooks should be fast and not block
- Handle errors - Return errors to prevent save/delete
- Use appropriate hooks - Use BeforeSave for common logic, BeforeCreate/BeforeUpdate for specific logic
- Don't modify relations in hooks - Modify relations separately
- Use context - Use context for cancellation and timeouts
See Also​
- Models Guide - Model usage guide
- Schema Reference - Model definition