Skip to content
Plugins

Hooks

React when something happens in Lettern, or change a value on its way through. Every hook is listed here.

On this page

Hooks let your plugin join in when Lettern does something. There are two kinds:

  • An action tells you something happened: an entry was published, a form was sent. You react.
  • A filter hands you a value on its way through, like an entry's URL or a page's HTML. You return it, changed or not.

#Use a hook

Register hooks in your provider's boot():

PHP
$hooks = $this->cms()->hooks();

// Action: react.
$hooks->on('entry.published', function (Entry $entry, ?User $user) {
    Http::post('https://hooks.example.com/published', ['url' => $entry->uri]);
});

// Filter: return the (possibly changed) first argument.
$hooks->addFilter('entry.uri', fn (string $uri, Entry $entry) => $entry->collection === 'jobs' ? '/careers'.$uri : $uri);

Both take an optional priority as a third argument: lower runs first, and the default is 10.

#Fire your own

Your plugin can offer hooks to others the same way. dispatch() fires an action, filter() passes a value through every filter and returns the result, and has() says whether anything listens:

PHP
$hooks->dispatch('reviews.approved', $review, $user);
$html = $hooks->filter('reviews.card', $html, $review);

Name them after your plugin, so they never clash with Lettern's own.

#Actions

#Content

ActionArgumentsFired when
entry.saved$entry, $user, $actionAn entry is created, saved, published or unpublished ($action says which).
entry.published$entry, $userAn entry goes live, also when a scheduled one comes due.
entry.unpublished$entry, $userAn entry is taken offline.
entry.deleting, entry.deleted$entry, $userAround deletion.
entry.uri-changed$entry, $old, $newA live entry's URL changed.
entry.structure-changed$entry, $userAn entry moves or its position changes. Its editorial draft stays unpublished; use this hook to invalidate structural output.
entries.reordered$collection, $ids, $userSiblings were reordered.
collection.saved, collection.deleted$collection / $handleA collection definition changed.
globals.saved$setA global set's fields or values changed.
navigation.saved, form.saved$handleA menu or form changed. (Templates and components are entries: use entry.saved and entry.published.)
redirects.savedThe redirect list changed.

#Media, forms and comments

ActionArgumentsFired when
media.uploaded$item, $userA file was added.
media.updated, media.deleted$itemA file changed or was removed.
form.submitted$form, $data, $submissionA valid submission arrived.
comment.created$comment, $entry, $authorSomeone commented.

#Settings and design

ActionArgumentsFired when
users.fields.savedThe user profile fields changed.
settings.saved$namespace, $valuesA settings namespace was saved (site is config/site.yaml).
design.savedThe design system or custom code changed.

#Publishing and rendering

ActionArgumentsFired when
site.page.built$uri, $entry, $file, $pageThe static build wrote a page.
site.page.removed$uri, $fileThe static build removed a page.
site.built$manifestA build finished.
render.begin$contextA page starts rendering.
render.head, render.foot$key, $htmlAdd markup to the page's head or the end of its body (deduplicated by key).
render.style$key, $css, $orderAdd CSS to the page (blocks use this).
render.depend$keyRecord that the page depends on something (entry:<id>, collection:<handle>), so the static build rebuilds it when that changes.
render.structured$key, $dataAdd JSON-LD to the page's graph.

#Abilities and people

ActionArgumentsFired when
ability.executing$ability, $input, $contextBefore any ability runs.
ability.executed$ability, $input, $result, $contextAfter it ran (letterncms/audit records changes here).
user.login$userSomeone signed in to the admin.
account.*Visitor accounts: signed up, verified, signed in, deleted and more. See Using accounts.
oauth.authorized$user, $clientAn agent was granted access.

#Filters

The first argument is the value you return; the rest are there to help you decide.

#Content

FilterValue, then argumentsLets you change
entry.creating$input, $collection, $userInput for a new entry.
entry.saving$entry, $collectionAn entry right before it is stored.
entry.uri$uri, $entry, $collectionAn entry's URL (Languages adds prefixes here).
collections$collectionsThe list of collections (add code-defined ones).
collections.defaults$definitionThe initial definition returned by collections/options. The Blocks module contributes an explicit blocks body; without it, a collection starts with no body. Creators apply explicit values afterward.
collections.settings$fields, $collectionThe settings a collection offers in Collections (Site adds its template picker).
revisions.keep$countHow many revisions each entry keeps (100).
gate.allows$allowed, $user, $entry, $ruleWhether someone may read a gated entry.
fields.options.<name>$optionsOptions of select fields with dynamic: <name> (templates, collections, timezones).
fields.definition.<name>$field, $contextResolved definition of a dynamic: <name> field. The collection host supplies $context['collection']; use it for presentation such as the inherited template name. Raw collection YAML stays unchanged.

#Abilities, settings and people

FilterValue, then argumentsLets you change
ability.input$input, $ability, $contextValidated input before the handler.
ability.result$result, $ability, $input, $contextWhat an ability returns.
settings.fields$fields, $namespaceA settings namespace's fields: add to another's, or take one over (Languages manages the site's language).
settings.saving$values, $namespace, $storedSettings right before they are validated and stored.
users.fields$fieldsThe fields of user profiles (add your own). A field with public: true can be edited on the public profile page.

#The admin and agents

FilterValue, then argumentsLets you change
admin.manifest$manifest, $userWhat the admin receives at start-up (modules add collections, globals, the Dashboard and config.<namespace> here).
admin.nav$itemsSidebar items that come and go (letterncms/content adds one per collection, letterncms/settings one per settings page).
agent.guide$guideThe instructions every AI agent gets.
mcp.prompts$promptsPrompts offered to MCP clients: the site's skills (cms()->skills()).
languages.translatornullThe machine translator (the assistant registers Claude).

#Pages

FilterValue, then argumentsLets you change
site.head$tags, $contextTags in a page's <head>.
site.foot$tags, $contextScripts at the end of a page's <body>.
site.scope$scope, $contextThe data templates and blocks can bind to (site, entry, globals, page, pagination…). $context->mode says who the page is for: only add a signed-in person's data in request, as a build goes to a public file.
site.html$html, $contextA rendered page.
site.build.extras$extrasExtra files the static build writes (sitemap, feeds…).
site.search.record$record, $entryA page's search index record (null leaves it out).
site.links.answered$answered, $pathWhether an address your plugin answers itself is real, so cms links:check accepts it.
seo.alternates$alternates, $entryhreflang alternates.
seo.sitemap$urlsAddresses your plugin answers itself, for the sitemap: ['uri' => …, 'updated' => …].

#Blocks

FilterValue, then argumentsLets you change
blocks.render.data$data, $typeWhat a block's render function receives.
blocks.data-sources$sourcesLists the Repeat block can loop over: see Data sources.
blocks.scope$scopeData for blocks rendered outside a page (Markdown twins, search).
blocks.variants$variantsBlock variants offered to editors.
blocks.folders, blocks.pattern-sources$sourcesFolders with blocks (block.json) or read-only patterns (<handle>.json).
blocks.breakpoints$breakpointsBreakpoints for block styles.

#Media, email, forms and menus

FilterValue, then argumentsLets you change
media.url, media.img$url, $image / $html, $image, $alt, $optionsThe address and markup of an image (point them at a CDN).
email.colors$colorsThe background, text and button colors of every email.
form.data$data, $form, $requestA submission before it is stored.
navigation.entry, navigation.itemHow menu items resolve.

Plugins add hooks of their own: see Using accounts and Commerce.