Skip to Content
AutomateEvent Hooks

Event Hooks

The event hook system lets you subscribe to item lifecycle operations across all collections. There are two hook types:

TypeBlockingUse case
Filter hook✅ YesModify payload before an operation — validation, transformation, enrichment
Action hook❌ NoSide effects after an operation — logging, notifications, cache invalidation

Event Names

Events follow the pattern [collection].items.[operation] or the global items.[operation].

EventDescription
items.queryBefore query execution — can modify query params
items.readAfter query, before return — can modify results
items.createItem creation (all collections)
items.updateItem update (all collections)
items.deleteItem deletion (all collections)
{collection}.items.queryQuery for a specific collection
{collection}.items.readRead for a specific collection
{collection}.items.createCreate for a specific collection
{collection}.items.updateUpdate for a specific collection
{collection}.items.deleteDelete for a specific collection
versions.createContent version created

Event Coverage

Not all built-in API routes emit events. Routes that use ItemsService internally emit events; routes using raw Supabase do not.

EndpointEvents emitted
GET/POST/PATCH/DELETE /api/items/[collection]/**✅ Full CRUD events
GET/POST/PATCH/DELETE /api/users/**✅ Full CRUD events on daas_users
GET/POST /api/roles/**✅ Full CRUD events on daas_roles
GET/POST /api/policies/**✅ Full CRUD events on daas_policies
GET/POST /api/workflows/**✅ Full CRUD events on daas_wf_definition
GET/POST /api/workflow-assignments/**✅ Full CRUD events on daas_wf_assignment
POST /api/workflow/transition✅ ItemsService events + custom workflow action event
GET /api/activity❌ Read-only, no hooks
GET/POST /api/fields/**❌ Raw Supabase — no events
GET/POST /api/collections/**❌ Raw Supabase — no events
GET/POST /api/relations/**❌ Raw Supabase — no events
GET/PATCH /api/settings❌ Raw Supabase — no events
GET/POST /api/files❌ Bypasses emitter

MCP tools for daas_users, daas_roles, daas_policies, daas_permissions, and daas_files all route through ItemsService and emit the standard item events.

Filter Hooks

Filter hooks run before the operation and can modify the payload. The operation waits for all filter hooks to complete before proceeding.

// extensions/my-extension/index.mjs export function register(sdk) { // Global — runs for every collection sdk.emitter.onFilter('items.create', async (payload, meta, context) => { return { ...payload, created_at: new Date().toISOString(), }; }); // Collection-specific validation sdk.emitter.onFilter('articles.items.create', async (payload, meta, context) => { if (!payload.title || payload.title.length < 5) { throw new Error('Article title must be at least 5 characters'); } // Auto-generate slug payload.slug = payload.title.toLowerCase().replace(/\s+/g, '-'); return payload; }); }

Filter hook signature

type FilterHandler<T = unknown> = ( payload: T, // Data being created/updated meta: Record<string, any>, // { collection, keys, ... } context: EventContext, // Accountability + services ) => T | Promise<T>;

Action Hooks

Action hooks run after the operation completes, asynchronously. They cannot modify the response.

export function register(sdk) { // Log all item creations sdk.emitter.onAction('items.create', async (meta, context) => { console.log('Item created:', meta.collection, meta.key); }); // Send notification when article published sdk.emitter.onAction('articles.items.update', async (meta, context) => { if (meta.payload?.status === 'published') { await fetch('https://notify.example.com/webhook', { method: 'POST', body: JSON.stringify({ article: meta.key }), }); } }); }

Action hook signature

type ActionHandler = ( meta: Record<string, any>, // { collection, key, payload, ... } context: EventContext, // Accountability + services ) => void | Promise<void>;

Event Context

Both hook types receive an EventContext:

type EventContext = { schema: SchemaOverview | null; accountability: { user: string; // User ID role: string | null; // Primary role ID admin: boolean; app: boolean; ip?: string; // IP address (optional) } | null; }

Available services

context.services is available inside runtime extensions (database-stored code). File-based extensions access services via the SDK (createItemsService, createVersionService). See Extensions for both patterns.

// Items CRUD on any collection const svc = await context.services.items('articles'); const items = await svc.readByQuery({ filter: { status: { _eq: 'published' } } }); await svc.createOne({ title: 'Hello' }); await svc.updateOne(id, { status: 'draft' }); await svc.deleteOne(id); // Schema services const collections = await context.services.collections(); const fields = await context.services.fields(); const relations = await context.services.relations(); // Files and versions const files = await context.services.files(); const versions = await context.services.versions(); // Send email await context.services.mail({ to: 'user@example.com', subject: 'Hi', html: '<p>Hello</p>' }); // Trigger / list cron jobs await context.services.cron.trigger('job-id-or-name'); const jobs = await context.services.cron.list(); // Whitelisted environment variables const apiKey = context.services.env.MY_API_KEY; // Custom services const notifier = await context.services.custom('slack-notify'); // Raw Supabase client (bypasses RLS — use with care) const { data } = await context.services.supabase.from('table').select('*'); // Safe HTTP (domain-restricted — configure EXTENSION_ALLOWED_DOMAINS) const res = await context.services.fetch('https://api.example.com/data');

Built-in Hooks

The platform registers several built-in action hooks automatically:

  • Audit logging — every create/update/delete writes a record to daas_activity
  • Workflow automation — creates/updates workflow instances when items match assignment rules
Last updated on