Skip to content

Lifecycle Hooks

Lifecycle hooks let you run custom code when content is created, updated, published, or deleted. Unlike webhooks (which fire HTTP requests to external URLs), lifecycle hooks run inside the WollyCMS server process and can modify data or block operations.

HookWhen it runsCan block?Can modify data?
beforeCreateBefore a page is insertedYesYes (via context.data)
afterCreateAfter a page is insertedNoNo
beforeUpdateBefore a page is updatedYesYes
afterUpdateAfter a page is updatedNoNo
beforeDeleteBefore a page is deletedYesNo
afterDeleteAfter a page is deletedNoNo
beforePublishBefore a page transitions to publishedYesYes
afterPublishAfter a page is publishedNoNo
beforeUnpublishBefore a page is unpublishedYesYes
afterUnpublishAfter a page is unpublishedNoNo

Additional hooks are available for media and blocks: beforeUpload, afterUpload, beforeMediaDelete, afterMediaDelete, beforeBlockCreate, afterBlockCreate, beforeBlockUpdate, afterBlockUpdate, beforeBlockDelete, afterBlockDelete.

Import the hooks registry and call hooks.on():

import { hooks } from '@wollycms/server/hooks';
// Block publishing if title is too short
hooks.on('beforePublish', async ({ data }) => {
if (data?.title && String(data.title).length < 10) {
throw new Error('Title must be at least 10 characters to publish');
}
});
// Sync to external search index after publish
hooks.on('afterPublish', async ({ entity, id }) => {
await fetch('https://search.example.com/index', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ id, title: entity?.title }),
});
});

Every hook handler receives a HookContext object:

interface HookContext {
entity?: Record<string, unknown>; // The existing entity (for update/delete)
data?: Record<string, unknown>; // Input data (for create/update) — mutable in "before" hooks
id?: number; // Entity ID (for update/delete)
user?: { id: number; email: string; role: string }; // The authenticated user
}
  • Run sequentially before the database operation
  • Can throw to abort the operation — the error message is returned to the client as a 400 response
  • Can modify context.data to transform input before it’s saved
  • If multiple handlers are registered, they run in registration order
// Transform data before save
hooks.on('beforeCreate', async ({ data }) => {
if (data?.title) {
data.title = String(data.title).trim();
}
});
// Block deletion of important pages
hooks.on('beforeDelete', async ({ entity }) => {
if (entity?.slug === 'home') {
throw new Error('Cannot delete the home page');
}
});
  • Run sequentially after the database operation
  • Cannot block the operation (it already happened)
  • Errors are logged but don’t affect the API response
  • Use for side effects: notifications, cache invalidation, external syncs
// Send Slack notification on new page
hooks.on('afterCreate', async ({ entity, user }) => {
await fetch(SLACK_WEBHOOK_URL, {
method: 'POST',
body: JSON.stringify({
text: `${user?.email} created "${entity?.title}"`,
}),
});
});
const handler = async ({ entity }) => { /* ... */ };
// Register
hooks.on('afterPublish', handler);
// Remove specific handler
hooks.off('afterPublish', handler);
// Remove all handlers for an event
hooks.clear('afterPublish');
// Remove all handlers
hooks.clear();

Register hooks in your server entry point or a dedicated hooks file that runs at startup. For self-hosted deployments, create a hooks.ts file and import it in your server entry:

hooks-setup.ts
import { hooks } from './hooks.js';
hooks.on('beforePublish', async ({ data }) => {
// Your custom logic
});

For Cloudflare Workers deployments, register hooks in the worker entry point before the Hono app handles requests.

FeatureLifecycle HooksWebhooks
Where it runsInside the server processExternal HTTP endpoint
Can block operationsYes (“before” hooks)No
Can modify dataYes (“before” hooks)No
Requires code deploymentYesNo (configured in admin UI)
Use caseValidation, transformation, server-side logicExternal notifications, CI/CD triggers

Use hooks for logic that must run reliably (validation, data transformation). Use webhooks for fire-and-forget notifications to external services.