Skip to content

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.

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 (with logger for log lines), context.spaceId, context.actor (who did it, or null for the system), and for content hooks context.contentType (the content type, with its technical name).

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.

HookWhen it runsPayload
content:beforeValidateA 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’sThe save: title, slug, locale, fields, spaceId
content:beforeCreateA new document passed the checks and is about to be writtenThe save, with checked fields
content:afterCreateA new document was savedThe saved document
content:beforeUpdateA changed document passed the checks and is about to be writtenThe save, with checked fields
content:afterUpdateA changed document was savedThe saved document
content:beforePublishA document is about to be published. Throw to stop itThe document
content:afterPublishA document was publishedThe published document
content:afterUnpublishA document was taken offline{ id }
content:beforeDeleteA document is about to be deleted. Throw to stop it{ id }
content:afterDeleteA document was deleted{ id, record }
contentType:afterCreateA content type was createdThe content type
contentType:afterUpdateA content type was changedThe content type
asset:afterUploadA file was uploaded{ id }
menu:afterWriteA menu was saved{ id, spaceId }
member:afterGrantSomeone got a role in a space, or their role changed{ spaceId, userId, role, previous }
workflow:afterRunA workflow run finished{ runId, workflowId, spaceId, status, trigger }
after:startThe CMS has startednone
before:stopThe CMS is shutting downnone

A saved document has, among others, id, title, slug, locale, status, fields (your field values by technical name) and permalink (its web address path).

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.

  1. Create summary-hook.ts next to content-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 } };
});
},
});
  1. Add summaryHook to the plugins list in content-model.ts (import it from './summary-hook.ts').
  2. 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.

  1. Add the address to .env:
Terminal window
DEPLOY_HOOK_URL=https://example.com/your-secret-build-hook
  1. Create deploy-hook.ts next to content-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');
}
});
},
});
  1. Add deployHook to the plugins list in content-model.ts and restart pnpm dev (a change to .env needs a restart).
  2. 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.

  • Hooks run one after another and are awaited, so an async function finishes before the next one starts. Keep them quick: the editor waits for them when saving.
  • A before hook 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 before hook stops the change, and the editor sees an error message.
  • Throwing in an after hook 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 in after hooks, as the deploy example does.
  • The order follows the priority option, 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 sync when 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.actor tells you who it was.