Initial commit
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
---
|
||||
title: Pick the Right Record Hook - Model vs Request vs Enrich
|
||||
impact: HIGH
|
||||
impactDescription: Wrong hook = missing request context, double-fired logic, or leaked fields in realtime events
|
||||
tags: hooks, onRecordEnrich, onRecordRequest, model-hooks, extending
|
||||
---
|
||||
|
||||
## Pick the Right Record Hook - Model vs Request vs Enrich
|
||||
|
||||
PocketBase v0.23+ splits record hooks into three families. Using the wrong one is the #1 source of "my hook doesn't fire" and "my hidden field still shows up in realtime events" bugs.
|
||||
|
||||
| Family | Examples | Fires for | Has request context? |
|
||||
|--------|----------|-----------|----------------------|
|
||||
| **Model hooks** | `OnRecordCreate`, `OnRecordAfterCreateSuccess`, `OnRecordValidate` | Any save path - Web API **and** cron jobs, custom commands, migrations, calls from other hooks | No - `e.Record`, `e.App`, **no** `e.RequestInfo` |
|
||||
| **Request hooks** | `OnRecordCreateRequest`, `OnRecordsListRequest`, `OnRecordViewRequest` | **Only** the built-in Web API endpoints | Yes - `e.RequestInfo`, `e.Auth`, HTTP headers/body |
|
||||
| **Enrich hook** | `OnRecordEnrich` | Every response serialization, **including realtime SSE events** and `apis.enrichRecord` | Yes, via `e.RequestInfo` |
|
||||
|
||||
**Incorrect (hiding a field in the request hook - leaks in realtime):**
|
||||
|
||||
```go
|
||||
// ❌ Only runs for GET /api/collections/users/records/{id}.
|
||||
// Realtime SSE subscribers still receive the "role" field.
|
||||
app.OnRecordViewRequest("users").BindFunc(func(e *core.RecordRequestEvent) error {
|
||||
e.Record.Hide("role")
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
**Correct (use `OnRecordEnrich` so realtime and list responses also hide the field):**
|
||||
|
||||
```go
|
||||
app.OnRecordEnrich("users").BindFunc(func(e *core.RecordEnrichEvent) error {
|
||||
e.Record.Hide("role")
|
||||
|
||||
// Add a computed field only for authenticated users
|
||||
if e.RequestInfo.Auth != nil {
|
||||
e.Record.WithCustomData(true) // required to attach non-schema data
|
||||
e.Record.Set("isOwner", e.Record.Id == e.RequestInfo.Auth.Id)
|
||||
}
|
||||
return e.Next()
|
||||
})
|
||||
```
|
||||
|
||||
```javascript
|
||||
// JSVM
|
||||
onRecordEnrich((e) => {
|
||||
e.record.hide("role");
|
||||
|
||||
if (e.requestInfo.auth?.collection()?.name === "users") {
|
||||
e.record.withCustomData(true);
|
||||
e.record.set("computedScore",
|
||||
e.record.get("score") * e.requestInfo.auth.get("base"));
|
||||
}
|
||||
e.next();
|
||||
}, "users");
|
||||
```
|
||||
|
||||
**Selection guide:**
|
||||
- Need to mutate the record before **any** save (API, cron, migration, nested hook)? → `OnRecordCreate` / `OnRecordUpdate` (pre-save) or `OnRecord*Success` (post-save).
|
||||
- Need access to HTTP headers, query params, or the authenticated client? → `OnRecord*Request`.
|
||||
- Need to hide fields, redact values, or attach computed props on responses including realtime? → **`OnRecordEnrich`** - this is the safest default for response shaping.
|
||||
- Need to validate before save? → `OnRecordValidate` (proxy over `OnModelValidate`).
|
||||
|
||||
Reference: [Go Record request hooks](https://pocketbase.io/docs/go-event-hooks/#record-crud-request-hooks) · [JS Record model hooks](https://pocketbase.io/docs/js-event-hooks/#record-model-hooks)
|
||||
Reference in New Issue
Block a user