Note: This document is a reference pointer. Complete documentation has been moved to the canonical hooks skill.
For comprehensive plugin hooks and event system documentation, see:
→ objectstack-platform/references/plugin-hooks.md
The canonical reference includes:
- Complete hook registration API (
ctx.hook,ctx.trigger) - All built-in hooks (kernel lifecycle + data events)
- Custom plugin event patterns
- Hook handler patterns and error handling
- Performance considerations
- Testing strategies
- Best practices
Register hook handlers in init() or start():
async init(ctx: PluginContext) {
// Kernel lifecycle hook
ctx.hook('kernel:ready', async () => {
ctx.logger.info('System ready');
});
// Data lifecycle hook
ctx.hook('data:beforeInsert', async (objectName, record) => {
if (objectName === 'task') {
record.created_at = new Date().toISOString();
}
});
}async start(ctx: PluginContext) {
await ctx.trigger('my-plugin:initialized', { version: '1.0.0' });
}Kernel Lifecycle:
kernel:ready— All plugins started, system validatedkernel:shutdown— Shutdown begins
Data Lifecycle:
data:beforeInsert— Before record createddata:afterInsert— After record createddata:beforeUpdate— Before record updateddata:afterUpdate— After record updateddata:beforeDelete— Before record deleteddata:afterDelete— After record deleteddata:beforeFind— Before querying recordsdata:afterFind— After querying records
Metadata:
metadata:changed— Metadata registered or updated
Follow the convention: {plugin-namespace}:{event-name}
// Trigger
await ctx.trigger('analytics:pageview', { path: '/dashboard', userId: '123' });
// Subscribe
ctx.hook('analytics:pageview', async (data) => {
console.log('Page viewed:', data.path);
});ctx.hook('data:beforeInsert', async (objectName, record) => {
if (objectName === 'task') {
record.status = record.status || 'pending';
}
});ctx.hook('data:afterInsert', async (objectName, record, result) => {
const audit = ctx.getService('audit');
await audit.log({
action: 'create',
object: objectName,
recordId: result.id,
});
});ctx.hook('data:afterUpdate', async (objectName, id, record, result) => {
if (objectName === 'opportunity' && record.stage === 'won') {
await ctx.trigger('sales:opportunity-won', { id, record: result });
}
});✅ DO:
- Use
before*for validation - Use
after*for side effects (notifications, logging, external API calls) - Keep hooks fast — especially
before*hooks - Use try/catch in
after*hooks — don't let one failure cascade - Follow naming convention:
{namespace}:{event-name} - Test hook handlers thoroughly
❌ DON'T:
- Don't block operations with slow external API calls in
before*hooks - Don't throw in
after*hooks (use try/catch and log errors) - Don't mutate arguments (except
recordinbefore*hooks) - Don't create circular dependencies between plugins
- Don't hook all objects unless necessary
Hooks execute in registration order within each plugin, then by plugin initialization order (based on dependencies).
- objectstack-data/SKILL.md#lifecycle-hooks — Complete hooks system overview
- objectstack-platform/references/plugin-hooks.md — Full plugin hooks documentation
- objectstack-data/references/data-hooks.md — Data lifecycle hooks
- Plugin Lifecycle — 3-phase plugin lifecycle
- Service Registry — DI container and service management
For complete documentation with detailed examples, hook context API, testing strategies, and performance optimization, see the canonical reference: