Custom Formatters
The section clamps a long title to one line and a long description to two, the way Google does. A formatter changes the text itself instead – truncate it at the length a search result actually shows, strip the HTML a textarea leaves behind, or vary it per page.
Defining a Formatter
Formatters live under johannschopplich.serp-preview.formatters, one for title and one for description. Each is a closure that receives the resolved value and the page, and returns what the snippet should draw:
use Kirby\Toolkit\Str;
return [
'johannschopplich.serp-preview' => [
'formatters' => [
'title' => fn (string $value, \Kirby\Cms\Page $page) => Str::short($value, 60),
'description' => fn (string $value, \Kirby\Cms\Page $page) => Str::short(strip_tags($value), 160)
]
]
];
Define only the one you need – a missing formatter leaves that line untouched.
What a Formatter Sees
The value arrives already resolved, in the order Title and Description Resolution lists, so a formatter never sees a {{ ... }} placeholder.
The page is the one being edited, which is what makes a formatter page-specific. On the site view it is the home page; on a page's file view the formatter does not run and the snippet draws the value as it is:
'title' => function (string $value, \Kirby\Cms\Page $page) {
// Articles carry the section in front of the title, other pages do not.
if ($page->intendedTemplate()->name() === 'article') {
return $page->parent()->title() . ': ' . $value;
}
return $value;
}
Where It Stops
A formatter shapes what the snippet shows and nothing else. It does not touch the content, the saved page, or the markup your frontend renders – if you want the same truncation in your <title> tag, call the same helper there.
A formatter runs again as the editor types, at most once per 250 ms. One that throws shows no error in the Panel: the snippet keeps the text it last drew, and the failed request is only in the browser's network tab. A formatter that fetches a remote URL delays the snippet, not the typing.