Advanced
Hooks
React to record events (create, update, delete) with async Python handlers. Drop scripts into the hooks/ directory — no registration required.
Hooks
FastCMS includes a user hook system that lets you run custom code when records are created, updated, or deleted — without modifying the core application.
How It Works
- Create a
.pyfile in thehooks/directory at the project root - Use decorator functions from
app.fastcms_hooks_apito register handlers - FastCMS auto-discovers and loads all hook files at startup
- Files starting with
_are ignored (use for shared utilities)
Quick Start
# hooks/notifications.py
from app.fastcms_hooks_api import on_record_create, on_record_update, on_record_delete
@on_record_create("posts")
async def on_new_post(event):
print(f"New post created: {event.record_id}")
print(f"Data: {event.data}")
@on_record_update("users")
async def on_user_update(event):
print(f"User {event.record_id} updated")
@on_record_delete("posts")
async def on_post_deleted(event):
print(f"Post {event.record_id} was deleted from {event.collection_name}")Decorators
| Decorator | Description |
|---|---|
@on_record_create(collection_name) | Fires after a record is created |
@on_record_update(collection_name) | Fires after a record is updated |
@on_record_delete(collection_name) | Fires after a record is deleted |
@on_any_event() | Fires on all record events in all collections |
Pass collection_name=None to match all collections:
@on_record_create(collection_name=None)
async def log_all_creates(event):
print(f"Record created in {event.collection_name}")Event Object
Every handler receives an event object:
| Property | Type | Description |
|---|---|---|
event.type | EventType | record.created, record.updated, or record.deleted |
event.collection_name | str | Name of the collection |
event.record_id | str | ID of the affected record |
event.data | dict | Full record data |
event.timestamp | str | ISO-8601 timestamp of when the event fired |
Real-World Examples
Send a Webhook
# hooks/webhooks.py
import httpx
from app.fastcms_hooks_api import on_record_create
@on_record_create("orders")
async def notify_fulfillment(event):
async with httpx.AsyncClient() as client:
await client.post(
"https://fulfillment.example.com/webhook",
json={
"order_id": event.record_id,
"order": event.data
}
)Sync to External Search Index
# hooks/search.py
from app.fastcms_hooks_api import on_record_create, on_record_update, on_record_delete
@on_record_create("products")
@on_record_update("products")
async def index_product(event):
# Add or update in search index
await search_client.index("products").save_object({
"objectID": event.record_id,
**event.data
})
@on_record_delete("products")
async def remove_from_index(event):
await search_client.index("products").delete_object(event.record_id)Listen to All Events
# hooks/audit.py
from app.fastcms_hooks_api import on_any_event
@on_any_event()
async def custom_audit_log(event):
print(
f"[{event.type.value}] {event.collection_name}/{event.record_id}"
)File Structure
project/
├── app/
├── hooks/
│ ├── notifications.py ← loaded automatically
│ ├── search.py ← loaded automatically
│ ├── _utils.py ← ignored (underscore prefix)
│ └── webhooks.py ← loaded automatically
└── ...Loaded Hooks Endpoint
Check which hooks are currently loaded:
GET /api/v1/hooks
Authorization: Bearer ADMIN_TOKEN[
{
"file": "hooks/notifications.py",
"module": "fastcms_hooks.notifications",
"functions": ["on_new_post", "on_user_update", "on_post_deleted"]
}
]Error Handling
If a hook file fails to import, FastCMS logs the error but continues starting normally. The failed hook is simply not loaded.
If a hook handler raises an exception at runtime, the error is logged but does not affect the original request that triggered the event.
Notes
- All handlers must be
async deffunctions - Hooks run after the database write — they cannot abort or modify the operation
- For blocking operations (pre-save validation), use Access Control Rules instead
- Restart the server after adding or modifying hook files