Database Hooks
Intercept database operations with lifecycle hooks.
Database hooks let you run custom logic before and after user and session operations. Use them for logging, validation, side effects, or rejecting operations.
Setup
use better_auth::hooks::DatabaseHooks;
use async_trait::async_trait;
struct AuditHook;
#[async_trait]
impl DatabaseHooks for AuditHook {
async fn after_create_user(
&self, user: &better_auth::types::User
) -> better_auth::error::AuthResult<()> {
println!("User created: {}", user.email.as_deref().unwrap_or(""));
Ok(())
}
}
let auth = BetterAuth::new(config)
.database(database)
.hook(AuditHook)
.build()
.await?;The DatabaseHooks Trait
All methods have default no-op implementations. Override only what you need.
#[async_trait]
pub trait DatabaseHooks: Send + Sync {
// User hooks
async fn before_create_user(&self, user: &mut CreateUser) -> AuthResult<()>;
async fn after_create_user(&self, user: &User) -> AuthResult<()>;
async fn before_update_user(&self, id: &str, update: &mut UpdateUser) -> AuthResult<()>;
async fn after_update_user(&self, user: &User) -> AuthResult<()>;
async fn before_delete_user(&self, id: &str) -> AuthResult<()>;
async fn after_delete_user(&self, id: &str) -> AuthResult<()>;
// Session hooks
async fn before_create_session(&self, session: &mut CreateSession) -> AuthResult<()>;
async fn after_create_session(&self, session: &Session) -> AuthResult<()>;
async fn before_delete_session(&self, token: &str) -> AuthResult<()>;
async fn after_delete_session(&self, token: &str) -> AuthResult<()>;
}Rejecting Operations
Return Err from a before_* hook to abort the operation:
#[async_trait]
impl DatabaseHooks for BlockDisposableEmails {
async fn before_create_user(
&self, user: &mut CreateUser
) -> AuthResult<()> {
if let Some(email) = &user.email {
if email.ends_with("@disposable.com") {
return Err(AuthError::forbidden("Disposable emails not allowed"));
}
}
Ok(())
}
}Modifying Data
before_* hooks receive mutable references, so you can modify the data before it's persisted:
#[async_trait]
impl DatabaseHooks for NormalizeEmail {
async fn before_create_user(
&self, user: &mut CreateUser
) -> AuthResult<()> {
if let Some(email) = &mut user.email {
*email = email.to_lowercase();
}
Ok(())
}
}Multiple Hooks
Multiple hooks can be registered. They execute in registration order:
let auth = BetterAuth::new(config)
.database(database)
.hook(AuditHook)
.hook(NormalizeEmail)
.hook(BlockDisposableEmails)
.build()
.await?;If any before_* hook returns an error, subsequent hooks and the operation itself are skipped.