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.

Hook Examples

Ready-to-use cloud function recipes for the most common hook patterns.

Auth Hooks

Invite-Only Registration

Only users with a valid invite code in their signup data can register. Hook: pre_register
def main():
    data = req.payload.get("data", {})
    invite_code = data.get("invite_code", "")

    if not invite_code:
        return {"block": True, "reason": "An invite code is required to sign up."}

    # Look up code in invites collection
    invite = db.find_one("invites",
        code=invite_code,
        used=False
    )

    if not invite:
        return {"block": True, "reason": "Invalid or expired invite code."}

    # Mark invite as used so it can't be reused
    db.update_document("invites", invite["id"], {"used": True})
    return {"block": False}

Block Disposable Emails

Reject signups from known throwaway email services. Hook: pre_register
BLOCKED_DOMAINS = [
    "mailinator.com", "tempmail.com", "guerrillamail.com",
    "throwam.com", "yopmail.com", "sharklasers.com",
]

def main():
    email = req.payload.get("email", "").lower()
    domain = email.split("@")[-1] if "@" in email else ""

    if domain in BLOCKED_DOMAINS:
        return {
            "block": True,
            "reason": "Disposable email addresses are not allowed. Please use a real email."
        }

    return {"block": False}

Onboarding: Create Default Records on Signup

Create a profile, default settings, and a welcome notification when a user registers. Hook: post_register
def main():
    user = req.payload.get("user", {})
    user_id = user["id"]
    data = user.get("data", {})

    # Create profile
    db.create_document("profiles", {
        "user_id": user_id,
        "display_name": data.get("name", ""),
        "avatar_url": None,
        "bio": "",
    })

    # Create default settings
    db.create_document("user_settings", {
        "user_id": user_id,
        "email_notifications": True,
        "theme": "light",
        "language": "en",
    })

    # Create welcome notification
    db.create_document("notifications", {
        "user_id": user_id,
        "type": "welcome",
        "message": f"Welcome to the app{', ' + data['name'] if data.get('name') else ''}! 🎉",
        "read": False,
    })

    return {"ok": True}

Suspend / Ban System

Block login for users flagged as banned, with a reason shown to the user. Hook: pre_login
def main():
    user = req.payload.get("user", {})
    data = user.get("data", {})

    if data.get("is_banned"):
        reason = data.get("ban_reason") or "Your account has been suspended. Contact support."
        return {"block": True, "reason": reason}

    # Optional: also block deactivated accounts
    if data.get("status") == "deactivated":
        return {
            "block": True,
            "reason": "Your account has been deactivated. Reactivate it at myapp.com/account."
        }

    return {"block": False}

Track Login Activity

Record every login with timestamp and increment a counter. Hook: post_login
def main():
    user = req.payload.get("user", {})
    user_id = user.get("id")
    data = user.get("data", {})

    # Update login stats on the user record
    db.update_app_user(user_id, data={
        "last_login_at": datetime.utcnow().isoformat(),
        "login_count": (data.get("login_count") or 0) + 1,
    })

    # Also write to a login history collection for audit purposes
    db.create_document("login_history", {
        "user_id": user_id,
        "email": user.get("email"),
        "logged_in_at": datetime.utcnow().isoformat(),
    })

    return {"ok": True}

Notify Team on New Signup (Slack)

Post a Slack message whenever a new user registers. Hook: post_register
def main():
    user = req.payload.get("user", {})
    slack_url = config.get("SLACK_WEBHOOK_URL")

    if not slack_url:
        return {"ok": True}

    name = user.get("data", {}).get("name") or user.get("email")
    http.post(slack_url, json={
        "text": f"🎉 New signup: *{name}* (`{user['email']}`)"
    })

    return {"ok": True}
Store SLACK_WEBHOOK_URL in your project’s environment config (Dashboard → Settings → Environment).

Collection Hooks

Auto-Timestamp Every Document

Add updated_at to every document automatically on save. Hook: pre_save
def main():
    doc = req.payload.get("document", {})
    op = req.payload.get("operation")

    # You can't modify the document from a hook (it's already committed or about to be)
    # Instead use this to write to a separate audit collection
    db.create_document("document_history", {
        "collection": req.payload.get("collection"),
        "operation": op,
        "snapshot": doc,
        "timestamp": datetime.utcnow().isoformat(),
    })

    return {"ok": True}
Note: Hooks cannot modify the document being saved. To set timestamps automatically, use cloud functions as your write endpoint instead, or handle it on the client side.

Sync to a Search Index

After a document is saved, push it to an external search service (e.g. Algolia, Meilisearch, Typesense). Hook: post_save
def main():
    doc = req.payload.get("document", {})
    doc_id = req.payload.get("document_id")
    op = req.payload.get("operation")
    collection = req.payload.get("collection")

    if collection != "posts":
        return {"ok": True}

    # Only index published posts
    if doc.get("status") != "published":
        return {"ok": True}

    search_api_key = config.get("TYPESENSE_API_KEY")
    search_host = config.get("TYPESENSE_HOST")

    http.put(
        f"{search_host}/collections/posts/documents/{doc_id}",
        json={
            "id": doc_id,
            "title": doc.get("title"),
            "body": doc.get("body"),
            "author": doc.get("author"),
        },
        headers={"X-TYPESENSE-API-KEY": search_api_key}
    )

    return {"ok": True}

Remove from Search Index on Delete

Hook: post_delete
def main():
    doc_id = req.payload.get("document_id")
    collection = req.payload.get("collection")

    if collection != "posts":
        return {"ok": True}

    search_api_key = config.get("TYPESENSE_API_KEY")
    search_host = config.get("TYPESENSE_HOST")

    http.delete(
        f"{search_host}/collections/posts/documents/{doc_id}",
        headers={"X-TYPESENSE-API-KEY": search_api_key}
    )

    return {"ok": True}

Archive Deleted Documents

Save a copy before deletion so data is never permanently lost. Hook: pre_delete
def main():
    doc = req.payload.get("document", {})
    doc_id = req.payload.get("document_id")
    collection = req.payload.get("collection")

    db.create_document("archive", {
        "source_collection": collection,
        "source_id": doc_id,
        "data": doc,
        "archived_at": datetime.utcnow().isoformat(),
    })

    return {"ok": True}

Notify on Document Change (Email)

Send an email when an order document’s status changes. Hook: post_save
def main():
    doc = req.payload.get("document", {})
    old = req.payload.get("old_data", {})
    op = req.payload.get("operation")

    # Only care about updates where status changed
    if op != "update":
        return {"ok": True}

    new_status = doc.get("status")
    old_status = old.get("status")

    if new_status == old_status:
        return {"ok": True}

    customer_email = doc.get("customer_email")
    if not customer_email:
        return {"ok": True}

    email.send(
        to=customer_email,
        subject=f"Order #{doc.get('order_number')} is now {new_status}",
        body=f"""
Hi there,

Your order #{doc.get('order_number')} status has been updated to: {new_status}.

Track your order at myapp.com/orders/{doc.get('id')}.

Thanks!
        """.strip()
    )

    return {"ok": True}

Tips

Access Project Config in Hooks

Store API keys and secrets in your project’s environment config (Dashboard → Settings → Environment), then read them with config.get():
api_key = config.get("MY_SERVICE_API_KEY")

Don’t Block on External Calls

For pre_login and pre_register hooks, always wrap external HTTP calls in try/except so a slow or failing external service doesn’t block your users:
def main():
    try:
        result = http.post("https://external.service.com/check", json={...})
        if result.get("blocked"):
            return {"block": True, "reason": result.get("reason")}
    except:
        pass  # fail-open — never block users due to external service issues

    return {"block": False}

One Function, Multiple Events

You can attach the same cloud function to multiple hook events and branch on req.payload.get("event"):
def main():
    event = req.payload.get("event")

    if event == "post_register":
        # handle registration
        pass
    elif event == "post_login":
        # handle login
        pass

    return {"ok": True}