Hooks
A hook is a named moment in the CMS, like “a document is about to be saved” or “a document was just published”. Your code can listen to a hook and runs every time that moment comes. You register hooks in a plugin.
There are two kinds of hooks:
- before hooks run before the change is made. They can adjust the data (fill in a field, clean up a title) or stop the change.
- after hooks run once the change is done. They react to it: call another service, write a log line.
How to register a hook
Section titled “How to register a hook”Inside a plugin’s setup function, call manablox.hooks.on with the hook’s name and a function:
import { definePlugin } from '@manablox/core';
export const myHooks = definePlugin({ name: 'my-hooks', setup(manablox) { manablox.hooks.on('content:afterPublish', (record, context) => { context.manablox.logger.info({ title: record.title }, 'published'); }); },});Your function gets two arguments:
- The payload: the data the hook is about, for example the document.
- The context:
context.manablox(withloggerfor log lines),context.spaceId,context.actor(who did it, ornullfor the system), and for content hookscontext.contentType(the content type, with its technicalname).
Save the file in your project folder, add the plugin to the plugins list in content-model.ts, and pnpm dev restarts with your hook active.
The most useful hooks
Section titled “The most useful hooks”| Hook | When it runs | Payload |
|---|---|---|
content:beforeValidate | A document is about to be checked and saved (new or existing). Return a changed payload to adjust fields; your changes are checked like an editor’s | The save: title, slug, locale, fields, spaceId |
content:beforeCreate | A new document passed the checks and is about to be written | The save, with checked fields |
content:afterCreate | A new document was saved | The saved document |
content:beforeUpdate | A changed document passed the checks and is about to be written | The save, with checked fields |
content:afterUpdate | A changed document was saved | The saved document |
content:beforePublish | A document is about to be published. Throw to stop it | The document |
content:afterPublish | A document was published | The published document |
content:afterUnpublish | A document was taken offline | { id } |
content:beforeDelete | A document is about to be deleted. Throw to stop it | { id } |
content:afterDelete | A document was deleted | { id, record } |
contentType:afterCreate | A content type was created | The content type |
contentType:afterUpdate | A content type was changed | The content type |
asset:afterUpload | A file was uploaded | { id } |
menu:afterWrite | A menu was saved | { id, spaceId } |
member:afterGrant | Someone got a role in a space, or their role changed | { spaceId, userId, role, previous } |
workflow:afterRun | A workflow run finished | { runId, workflowId, spaceId, status, trigger } |
after:start | The CMS has started | none |
before:stop | The CMS is shutting down | none |
A saved document has, among others, id, title, slug, locale, status, fields (your field values by technical name) and permalink (its web address path).
Example: fill in a summary
Section titled “Example: fill in a summary”Editors often forget the summary of an article. This hook copies the title into an empty summary field before the document is checked. It assumes a content type with the technical name article and a text field summary.
- Create
summary-hook.tsnext tocontent-model.ts:
import { definePlugin } from '@manablox/core';
export const summaryHook = definePlugin({ name: 'summary-hook', setup(manablox) { manablox.hooks.on('content:beforeValidate', (input, context) => { if (context.contentType.name !== 'article') return; if (input.fields.summary) return; return { ...input, fields: { ...input.fields, summary: input.title } }; }); },});- Add
summaryHookto thepluginslist incontent-model.ts(import it from'./summary-hook.ts'). - In the admin, save an article with an empty summary.
After saving, the summary field holds the title. Returning nothing (return;) leaves the document as it is, which is why the hook returns early for other content types and for articles that already have a summary.
Example: rebuild the website after publishing
Section titled “Example: rebuild the website after publishing”Some website hosts give you a “deploy hook”, a secret web address that starts a new build of your site when something calls it. This hook calls it whenever a document is published.
- Add the address to
.env:
DEPLOY_HOOK_URL=https://example.com/your-secret-build-hook- Create
deploy-hook.tsnext tocontent-model.ts:
import { definePlugin } from '@manablox/core';
export const deployHook = definePlugin({ name: 'deploy-hook', setup(manablox) { manablox.hooks.on('content:afterPublish', async (record, context) => { const url = process.env.DEPLOY_HOOK_URL; if (!url) return; try { await fetch(url, { method: 'POST' }); context.manablox.logger.info({ title: record.title }, 'deploy hook called'); } catch (error) { context.manablox.logger.warn({ err: error }, 'deploy hook failed'); } }); },});- Add
deployHookto thepluginslist incontent-model.tsand restartpnpm dev(a change to.envneeds a restart). - Publish a document.
The terminal shows “deploy hook called” with the document’s title. The try and catch matter: see the rules below.
In a docker project, Compose hands every variable in .env to the api container, where hooks run, so DEPLOY_HOOK_URL needs no extra step. Rebuild the image for the new plugin file as described in Plugins.
The rules
Section titled “The rules”- Hooks run one after another and are awaited, so an
asyncfunction finishes before the next one starts. Keep them quick: the editor waits for them when saving. - A
beforehook may return a changed copy of the payload, which the next hook and the save then use. Returning nothing keeps the payload as it is. - Throwing an error in a
beforehook stops the change, and the editor sees an error message. - Throwing in an
afterhook does not undo the change (it has already happened), but the editor still gets an error, and the work that would have come after your hook is skipped. That includes the CMS’s own follow-up work, like starting workflows, sending webhooks and clearing the cache. So always catch errors inafterhooks, as the deploy example does. - The order follows the
priorityoption, lower first; the default is 100:manablox.hooks.on('content:afterPublish', handler, { priority: 50 }). - Content hooks run in every process that changes content: the admin’s process, and
manablox syncwhen it writes templates. The public API never changes content, so they do not run there. - Hooks also run for changes made by workflows and API keys, not only by people in the admin.
context.actortells you who it was.