> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/pocketbase/pocketbase/llms.txt
> Use this file to discover all available pages before exploring further.

# Events

> Event hooks and lifecycle management in PocketBase

PocketBase provides a comprehensive event system that allows you to execute custom logic at specific points in the application lifecycle.

## Event types

PocketBase events are categorized into several groups:

* **App events** - Application lifecycle events
* **Model events** - Generic database model events
* **Record events** - Specific to record operations
* **Collection events** - Collection schema changes
* **Request events** - HTTP API request events

## App events

### OnBootstrap

Triggered when initializing app resources:

```go theme={null}
app.OnBootstrap().BindFunc(func(e *core.BootstrapEvent) error {
    log.Println("App is bootstrapping")
    return e.Next()
})
```

### OnServe

Triggered when the web server starts:

```go theme={null}
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
    // Add custom routes
    e.Router.GET("/hello", func(re *core.RequestEvent) error {
        return re.String(200, "Hello, World!")
    })
    
    return e.Next()
})
```

### OnTerminate

Triggered when the app is being terminated:

```go theme={null}
app.OnTerminate().BindFunc(func(e *core.TerminateEvent) error {
    log.Println("App is shutting down")
    // Cleanup logic
    return e.Next()
})
```

### OnBackupCreate

Triggered when creating a backup:

```go theme={null}
app.OnBackupCreate().BindFunc(func(e *core.BackupEvent) error {
    log.Printf("Creating backup: %s", e.Name)
    return e.Next()
})
```

### OnBackupRestore

Triggered before restoring a backup:

```go theme={null}
app.OnBackupRestore().BindFunc(func(e *core.BackupEvent) error {
    log.Printf("Restoring backup: %s", e.Name)
    return e.Next()
})
```

## Record events

### OnRecordCreate

Triggered when creating a new record:

```go theme={null}
// For all collections
app.OnRecordCreate().BindFunc(func(e *core.RecordEvent) error {
    log.Printf("Creating record in %s", e.Record.Collection().Name)
    return e.Next()
})

// For specific collection
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordEvent) error {
    // Set default values
    if e.Record.GetInt("views") == 0 {
        e.Record.Set("views", 0)
    }
    return e.Next()
})
```

### OnRecordUpdate

Triggered when updating a record:

```go theme={null}
app.OnRecordUpdate("posts").BindFunc(func(e *core.RecordEvent) error {
    // Track changes
    original := e.Record.Original()
    
    if e.Record.GetString("title") != original.GetString("title") {
        log.Println("Title was changed")
    }
    
    return e.Next()
})
```

### OnRecordDelete

Triggered when deleting a record:

```go theme={null}
app.OnRecordDelete("posts").BindFunc(func(e *core.RecordEvent) error {
    log.Printf("Deleting post: %s", e.Record.GetString("title"))
    return e.Next()
})
```

### OnRecordValidate

Triggered during record validation:

```go theme={null}
app.OnRecordValidate("posts").BindFunc(func(e *core.RecordEvent) error {
    // Custom validation
    if len(e.Record.GetString("title")) < 5 {
        return errors.New("title must be at least 5 characters")
    }
    return e.Next()
})
```

## Event execution phases

Record events have multiple execution phases:

### Before execution

`OnRecordCreate`, `OnRecordUpdate`, `OnRecordDelete` run BEFORE validation and database operations:

```go theme={null}
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordEvent) error {
    // Runs BEFORE validation and INSERT
    e.Record.Set("created_at", time.Now())
    return e.Next()
})
```

### Execute phase

`OnRecordCreateExecute`, `OnRecordUpdateExecute`, `OnRecordDeleteExecute` run right before the database operation:

```go theme={null}
app.OnRecordCreateExecute("posts").BindFunc(func(e *core.RecordEvent) error {
    // Runs after validation, right before INSERT
    log.Println("About to insert into database")
    return e.Next()
})
```

### After success

`OnRecordAfterCreateSuccess`, `OnRecordAfterUpdateSuccess`, `OnRecordAfterDeleteSuccess` run after successful database persistence:

```go theme={null}
app.OnRecordAfterCreateSuccess("posts").BindFunc(func(e *core.RecordEvent) error {
    // Runs after successful INSERT
    // Safe to perform external API calls here
    return e.Next()
})
```

<Info>
  In transactions, "After Success" hooks are delayed until the transaction commits.
</Info>

### After error

`OnRecordAfterCreateError`, `OnRecordAfterUpdateError`, `OnRecordAfterDeleteError` run after failed operations:

```go theme={null}
app.OnRecordAfterCreateError("posts").BindFunc(func(e *core.RecordErrorEvent) error {
    log.Printf("Failed to create record: %v", e.Error)
    return e.Next()
})
```

## Collection events

### OnCollectionCreate

Triggered when creating a collection:

```go theme={null}
app.OnCollectionCreate().BindFunc(func(e *core.CollectionEvent) error {
    log.Printf("Creating collection: %s", e.Collection.Name)
    return e.Next()
})
```

### OnCollectionUpdate

Triggered when updating a collection:

```go theme={null}
app.OnCollectionUpdate().BindFunc(func(e *core.CollectionEvent) error {
    log.Printf("Updating collection: %s", e.Collection.Name)
    return e.Next()
})
```

### OnCollectionDelete

Triggered when deleting a collection:

```go theme={null}
app.OnCollectionDelete().BindFunc(func(e *core.CollectionEvent) error {
    if e.Collection.Name == "important" {
        return errors.New("cannot delete this collection")
    }
    return e.Next()
})
```

## Request events

### Custom routes

Add custom HTTP endpoints:

```go theme={null}
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
    e.Router.GET("/api/custom", func(re *core.RequestEvent) error {
        data := map[string]any{
            "message": "Hello from custom route",
        }
        return re.JSON(200, data)
    } /* optional middlewares */)
    
    return e.Next()
})
```

### OnRecordsListRequest

Triggered on listing records via API:

```go theme={null}
app.OnRecordsListRequest("posts").BindFunc(func(e *core.RecordsListRequestEvent) error {
    log.Printf("Listing %d posts", len(e.Records))
    return e.Next()
})
```

### OnRecordViewRequest

Triggered when viewing a single record:

```go theme={null}
app.OnRecordViewRequest("posts").BindFunc(func(e *core.RecordRequestEvent) error {
    // Increment view count
    e.Record.Set("views+", 1)
    app.Save(e.Record)
    return e.Next()
})
```

## Hook binding

### BindFunc()

Bind a function to an event:

```go theme={null}
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordEvent) error {
    // Your logic here
    return e.Next()
})
```

### Bind()

Bind a handler with custom ID and priority:

```go theme={null}
import "github.com/pocketbase/pocketbase/tools/hook"

app.OnRecordCreate("posts").Bind(&hook.Handler[*core.RecordEvent]{
    Id: "my-custom-handler",
    Priority: 100, // Higher = runs later
    Func: func(e *core.RecordEvent) error {
        return e.Next()
    },
})
```

### Unbind()

Remove a specific handler:

```go theme={null}
app.OnRecordCreate("posts").Unbind("my-custom-handler")
```

## Event priorities

Control execution order with priorities:

```go theme={null}
// Runs first (lower priority number)
app.OnRecordCreate("posts").Bind(&hook.Handler[*core.RecordEvent]{
    Id: "handler-1",
    Priority: -100,
    Func: func(e *core.RecordEvent) error {
        log.Println("First")
        return e.Next()
    },
})

// Runs last (higher priority number)
app.OnRecordCreate("posts").Bind(&hook.Handler[*core.RecordEvent]{
    Id: "handler-2",
    Priority: 100,
    Func: func(e *core.RecordEvent) error {
        log.Println("Last")
        return e.Next()
    },
})
```

<Info>
  Default priority is 0. Lower numbers run earlier, higher numbers run later.
</Info>

## Event propagation

### e.Next()

Continue to the next handler or core logic:

```go theme={null}
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordEvent) error {
    // Run your logic
    log.Println("Before save")
    
    // Continue execution
    if err := e.Next(); err != nil {
        return err
    }
    
    // Run logic after
    log.Println("After save")
    return nil
})
```

### Stopping propagation

Return an error to stop execution:

```go theme={null}
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordEvent) error {
    if e.Record.GetString("status") == "banned" {
        return errors.New("cannot create banned posts")
    }
    return e.Next()
})
```

## Auth events

### OnRecordAuthRequest

Triggered after successful authentication:

```go theme={null}
app.OnRecordAuthRequest("users").BindFunc(func(e *core.RecordAuthRequestEvent) error {
    log.Printf("User %s authenticated via %s", 
        e.Record.Email(), 
        e.AuthMethod,
    )
    return e.Next()
})
```

### OnRecordAuthWithPasswordRequest

Triggered during password authentication:

```go theme={null}
app.OnRecordAuthWithPasswordRequest("users").BindFunc(func(e *core.RecordAuthWithPasswordRequestEvent) error {
    // Log login attempts
    log.Printf("Login attempt for: %s", e.Identity)
    return e.Next()
})
```

### OnRecordAuthWithOAuth2Request

Triggered during OAuth2 authentication:

```go theme={null}
app.OnRecordAuthWithOAuth2Request("users").BindFunc(func(e *core.RecordAuthWithOAuth2RequestEvent) error {
    if e.IsNewRecord {
        // Set default values for new OAuth2 users
        e.Record.Set("verified", true)
    }
    return e.Next()
})
```

## Mail events

### OnMailerSend

Intercept all outgoing emails:

```go theme={null}
app.OnMailerSend().BindFunc(func(e *core.MailerEvent) error {
    log.Printf("Sending email to: %v", e.Message.To)
    return e.Next()
})
```

### OnMailerRecordAuthAlertSend

Intercept auth alert emails:

```go theme={null}
app.OnMailerRecordAuthAlertSend("users").BindFunc(func(e *core.MailerRecordEvent) error {
    // Customize auth alert email
    e.Message.Subject = "Security Alert: New Login"
    return e.Next()
})
```

## Best practices

### Keep hooks focused

```go theme={null}
// Good - single responsibility
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordEvent) error {
    return validatePostContent(e.Record)
})

app.OnRecordCreate("posts").BindFunc(func(e *core.RecordEvent) error {
    return notifySubscribers(e.Record)
})

// Bad - too many responsibilities
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordEvent) error {
    validatePostContent(e.Record)
    notifySubscribers(e.Record)
    updateStatistics(e.Record)
    sendWebhook(e.Record)
    return e.Next()
})
```

### Use appropriate event phases

```go theme={null}
// Before database operation - modify data
app.OnRecordCreate("posts").BindFunc(func(e *core.RecordEvent) error {
    e.Record.Set("slug", generateSlug(e.Record.GetString("title")))
    return e.Next()
})

// After successful operation - external actions
app.OnRecordAfterCreateSuccess("posts").BindFunc(func(e *core.RecordEvent) error {
    return sendNotification(e.Record) // Safe for external APIs
})
```

### Handle errors gracefully

```go theme={null}
app.OnRecordAfterCreateSuccess("posts").BindFunc(func(e *core.RecordEvent) error {
    if err := sendWebhook(e.Record); err != nil {
        // Log but don't fail the request
        e.App.Logger().Error("Webhook failed", "error", err)
    }
    return e.Next()
})
```
