Tutorial 8: Event Hooks & Extensions
So far, all your logic lives in the frontend. But some rules need to run on the server — validation that can’t be bypassed by a crafty API call, notifications triggered by data changes, and scheduled jobs that run whether or not anyone has the app open.
DaaS provides three extension mechanisms:
- Filter hooks — blocking middleware that can validate or transform data before an operation completes
- Action hooks — non-blocking side effects that fire after an operation succeeds
- Cron jobs — scheduled JavaScript that runs on a timer
By the end you’ll have:
- A filter hook that rejects tasks with a
due_datein the past on creation - An action hook that fires a notification when
assigned_tochanges on a task - A cron job that escalates overdue tasks to
highpriority every day at 8am - All hooks visible and debuggable via the DaaS Logs panel (
/logs).
Prerequisites
- Completed Tutorial 7: File Uploads.
- Familiarity with basic JavaScript (hooks and cron jobs are written as
.mjsfiles). - Your AI IDE must have active MCP connections — see AI IDE Setup if not configured.
Runtime vs. file extensions — Runtime extensions are stored in the daas_extensions database table, hot-reload automatically, and are fully manageable by your AI assistant via MCP tools. File-based extensions in extensions/<name>/index.mjs require a server restart and cannot be managed by AI. This tutorial uses runtime extensions.
Create a Filter Hook: Validate Due Date
A filter hook on items.create can inspect the incoming payload and throw an error to abort the operation. DaaS will return that error to the caller — no item is created.
Agent mode required — In VS Code, open Copilot Chat and switch to agent mode (⚡). In Kiro, open the Kiro Chat panel. Your AI has access to the mcp_daas_extensions tool via MCP.
In your AI chat panel, give your AI this prompt:
Create a runtime filter hook named "validate-task-due-date" on the "items.create" event. It should check the payload's due_date field and throw an error if the date is in the past (compare by day only). The hook should only apply to the "tasks" collection. Make it active.Your AI will create code similar to this:
if (meta.collection !== "tasks") return payload;
if (payload.due_date) {
const due = new Date(payload.due_date);
const now = new Date();
now.setHours(0, 0, 0, 0);
if (due < now) {
throw new Error("Due date cannot be in the past.");
}
}
return payload;The hook runs immediately — no restart required.
Structured errors — Use throw new HookError(message, statusCode, code) instead of throw new Error(...) to set a custom HTTP status code and error code on the response. HookError is available in all runtime extensions and cron jobs.
Test it: Try creating a task via the form or API with a due_date yesterday. The request should be rejected with "Due date cannot be in the past.".
Filter hooks run synchronously in the request path. Keep them fast — avoid external HTTP calls inside filter hooks. Use action hooks for side effects.
Create an Action Hook: Assignment Notification
An action hook on items.update fires after the update succeeds. Use it to send a notification when assigned_to changes.
Agent mode required — In VS Code, open Copilot Chat and switch to agent mode (⚡). In Kiro, open the Kiro Chat panel. Your AI has access to the mcp_daas_extensions tool via MCP.
In your AI chat panel, give your AI this prompt:
Create a runtime action hook named "notify-task-assignment" on the "items.update" event. It should only fire for the "tasks" collection and only when "assigned_to" is in the changed payload. For each updated task, read the title and assigned_to fields and log a notification message to the console. Make it active.Your AI will create code similar to this:
if (meta.collection !== "tasks") return;
if (!meta.payload.assigned_to) return;
const tasksService = await services.items("tasks");
for (const id of meta.keys) {
const task = await tasksService.readOne(id, {
fields: ["title", "assigned_to"],
});
// Log the assignment — replace with your notification service
console.log(
`[notify] Task "${task.title}" assigned to user ${task.assigned_to}`
);
// Example: POST to an external notification endpoint via services.fetch
// await services.fetch("https://your-notifications.example.com/send", {
// method: "POST",
// headers: { "Content-Type": "application/json" },
// body: JSON.stringify({
// userId: task.assigned_to,
// message: `You have been assigned to "${task.title}"`,
// }),
// });
}Test it: Edit a task and change assigned_to. Open the DaaS Logs panel at /logs, filter by source extension — you should see the [notify] log message.
HTTP requests from extensions — Use await services.fetch(url, options) instead of the global fetch(). The sandbox blocks fetch for security; services.fetch enforces domain allowlisting configured via the EXTENSION_ALLOWED_DOMAINS environment variable.
Verify Hooks in the Logs Panel
DaaS logs all hook executions. To monitor them live:
- In the DaaS admin UI, go to Logs (
/logs). - Set source to
extension. - Trigger a task creation or update in your app.
- Watch logs appear in real time.
![]()
Create a Cron Job: Escalate Overdue Tasks
A cron job runs on a schedule. Create one that sets priority to high for any task that is past its due_date and not yet done.
Agent mode required — In VS Code, open Copilot Chat and switch to agent mode (⚡). In Kiro, open the Kiro Chat panel. Your AI has access to the mcp_daas_cron tool via MCP.
In your AI chat panel, type the slash command:
/create-cron "escalate-overdue-tasks" dailyWhen prompted, provide the details: timezone Asia/Singapore, runs at 8am (0 8 * * *), escalates overdue tasks by setting priority to high.
Your AI will create code similar to this:
const tasksService = await services.items("tasks");
const today = new Date().toISOString().split("T")[0];
const overdue = await tasksService.readByQuery({
filter: {
_and: [
{ due_date: { _lt: today } },
{ status: { _neq: "done" } },
{ priority: { _neq: "high" } },
],
},
fields: ["id", "title"],
});
if (overdue.length === 0) {
console.log("[escalate] No overdue tasks found.");
return;
}
for (const task of overdue) {
await tasksService.updateOne(task.id, { priority: "high" });
console.log(`[escalate] Escalated task "${task.title}" to high priority.`);
}
console.log(`[escalate] Done. Escalated ${overdue.length} task(s).`);The cron job will run at its next scheduled time. To test immediately, ask your AI:
Run the escalate-overdue-tasks cron job now and show me the execution logs.Cron jobs have access to all DaaS services — they can query and mutate any collection. They run with admin-level access and bypass RBAC policies.
Run the Full E2E Check
Verify all hooks work together:
Validation hook
- Create a task with
due_date= yesterday → should fail with validation error - Create a task with
due_date= tomorrow → should succeed
Extension Quick Reference
| Type | Runs | Blocking | Use for | Scope |
|---|---|---|---|---|
| Filter hook | Before operation | Yes — can abort | Validation, payload transformation | Runtime & file-based |
| Action hook | After operation | No | Notifications, audit logs, side effects | Runtime & file-based |
| Cron job | On a timer | No | Batch processing, scheduled checks | Database-stored |
| Custom endpoint | On HTTP request | N/A | Custom REST routes | File-based only |
Hook Events Reference
| Event | Triggered when |
|---|---|
items.query | Any read query on a collection |
items.read | A specific item is read |
items.create | An item is created |
items.update | An item is updated |
items.delete | An item is deleted |
Congratulations
You’ve built a complete project management app with Buildpad — from data schema to UI, access control, workflows, file uploads, and server-side automation. Here’s what you covered across all 8 tutorials:
| Tutorial | Feature |
|---|---|
| 1 | Project scaffold + Phase 0 foundation |
| 2 | Collections & field types |
| 3 | CollectionList + CollectionForm UI |
| 4 | RBAC — Manager and Member roles |
| 5 | Workflow state machine + WorkflowButton |
| 6 | M2O and M2M relations |
| 7 | File and image uploads |
| 8 | Filter hooks, action hooks, cron jobs |
Where to Go Next
- AI IDE Setup — Explore all 18 agent skills available in your starter.
- Organizations — Invite your team and manage roles at the org level.
- DaaS API Reference — Full REST API reference for every endpoint.