---
title: "Custom Formatters"
description: "Shorten or clean the title and description before the snippet draws them, with a closure in your config."
canonical_url: "https://kirby.tools/docs/serp-preview/formatters"
---

# Custom Formatters

> Shorten or clean the title and description before the snippet draws them, with a closure in your config.

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:

```php [site/config/config.php]
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: the content field if one is named and filled, otherwise the fallback, otherwise the composed page and site title. Queries in the blueprint have run by then, 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, while a site or user file is formatted against the home page:

```php [site/config/config.php]
'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;
}
```

<note>

A formatter runs on the server, so it can reach anything Kirby can – other pages, files, the site. The closure itself never leaves your config; the Panel only learns that one exists.

</note>

## 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.

Each change posts the current value to your site, at most once per 250 ms, title and description sharing that window. The reply replaces the drawn text; a failed reply keeps the previous one, so a formatter that throws shows no error in the Panel 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.

---

Every page of this site as Markdown: <https://kirby.tools/sitemap.md>
