🚧 FastCMS is under active development — not ready for production use. APIs and features may change without notice.
FastCMS
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

  1. Create a .py file in the hooks/ directory at the project root
  2. Use decorator functions from app.fastcms_hooks_api to register handlers
  3. FastCMS auto-discovers and loads all hook files at startup
  4. 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

DecoratorDescription
@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:

PropertyTypeDescription
event.typeEventTyperecord.created, record.updated, or record.deleted
event.collection_namestrName of the collection
event.record_idstrID of the affected record
event.datadictFull record data
event.timestampstrISO-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 def functions
  • 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

On this page