Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.cocobase.cc/llms.txt

Use this file to discover all available pages before exploring further.

Collection Hooks

Collection hooks fire when documents in a specific collection change. Attach them per-collection through the Dashboard.

Events

pre_save

Fires before a document is created or updated. Runs in the background (does not block the write). Use it to: enrich data before saving, send a notification that something is about to be written, log an audit event. Payload:
req.payload = {
  "event": "pre_save",
  "collection": "posts",
  "operation": "create",          # "create" or "update"
  "document": {                   # the data being saved
    "title": "Hello World",
    "status": "draft"
  },
  "old_data": { ... }             # only present on "update" — the previous data
}
Example — log all creates to an external audit service:
def main():
    op = req.payload.get("operation")
    doc = req.payload.get("document", {})
    collection = req.payload.get("collection")

    if op == "create":
        http.post("https://audit.myapp.com/events", json={
            "action": "document_created",
            "collection": collection,
            "data": doc,
        })

    return {"ok": True}

post_save

Fires after a document is successfully created or updated. Runs in the background. Use it to: send notifications, update a search index, trigger downstream workflows. Payload:
req.payload = {
  "event": "post_save",
  "collection": "posts",
  "operation": "create",          # "create" or "update"
  "document": {                   # the final saved data
    "title": "Hello World",
    "status": "published"
  },
  "document_id": "doc-uuid",
  "old_data": { ... }             # only present on "update"
}
Example — send a push notification when a post is published:
def main():
    doc = req.payload.get("document", {})
    old = req.payload.get("old_data", {})

    # Only notify when status changes to published
    if doc.get("status") == "published" and old.get("status") != "published":
        author_id = doc.get("author_id")
        author = db.get_app_user(author_id) if author_id else None

        if author and author.get("fcm_token"):
            http.post("https://fcm.googleapis.com/v1/messages", json={
                "token": author["fcm_token"],
                "notification": {
                    "title": "Post published!",
                    "body": doc.get("title"),
                }
            })

    return {"ok": True}

pre_delete

Fires before a document is deleted. Runs in the background. Use it to: archive data elsewhere before it’s gone, clean up related records, send a warning notification. Payload:
req.payload = {
  "event": "pre_delete",
  "collection": "posts",
  "document_id": "doc-uuid",
  "document": {                   # snapshot of the document about to be deleted
    "title": "Hello World",
    "author_id": "user-123"
  }
}
Example — archive to a separate collection before deleting:
def main():
    doc = req.payload.get("document", {})
    doc_id = req.payload.get("document_id")

    db.create_document("deleted_posts", {
        "original_id": doc_id,
        "data": doc,
        "deleted_at": datetime.utcnow().isoformat(),
    })

    return {"ok": True}

post_delete

Fires after a document has been deleted. Runs in the background. Use it to: clean up dependent data, update counters, remove from search index. Payload:
req.payload = {
  "event": "post_delete",
  "collection": "posts",
  "document_id": "doc-uuid",
  "document": {                   # snapshot of the deleted document
    "title": "Hello World",
    "author_id": "user-123"
  }
}
Example — decrement an author’s post count:
def main():
    doc = req.payload.get("document", {})
    author_id = doc.get("author_id")

    if author_id:
        author = db.get_app_user(author_id)
        if author:
            current_count = author.get("data", {}).get("post_count", 0)
            db.update_app_user(author_id, data={
                "post_count": max(0, current_count - 1)
            })

    return {"ok": True}

Handling Both pre_save Operations

If one function handles both create and update, check operation:
def main():
    op = req.payload.get("operation")    # "create" or "update"
    doc = req.payload.get("document", {})
    old = req.payload.get("old_data", {})

    if op == "create":
        # New document logic
        pass
    elif op == "update":
        # Changed fields
        changed = {
            k: v for k, v in doc.items()
            if old.get(k) != v
        }
        pass

    return {"ok": True}

Rules vs Hooks

Collection hooks are different from collection rules:
RulesHooks
PurposeAllow/deny accessRun side-effects
WhereDashboard → Collection → RulesDashboard → Collection → Settings
Blocking✅ Yes — denies the request❌ No — background only
Receives dataNo✅ Full document payload
Use rules to gate who can write. Use hooks to react to what was written.

Limits & Behaviour

  • Hooks run in background threads — they never add latency to your API response.
  • If a hook function fails or times out (15 second limit), the error is logged but the document operation is not rolled back.
  • You can attach multiple hooks to the same event on the same collection — all of them fire.
  • Hooks only fire on operations made through the Cocobase API/SDK, not direct DB writes.