Kirby SEO - Serp preview field for the panel

Maybe I’m creating too many plugins for my own good right now, but here is another one…

Kirby SEO

A serp preview field for the panel.

The black thing in the recording is not a bug. It’s a click to get the slug edit modal. It just did not fit in the screen.

What is SEO?

It stands for Search Engine Optimization. Make yourself visible in the search engines.


  1. Place the fields/seo folder in site/fields. The field for the panel.
  2. Place the plugins/seo folder in site/plugins. The helper functions for your site.


    type: seo

You can add a label on it if you like, but you don’t need to.

Call in template / snippet / pattern

In my header.php I do like this:

echo $page->seo()->seoTitle('html');
echo $page->seo()->seoDescription('html');

If you want full control over the HTML, don’t add html as parameter, like this:

<title><?php echo $page->seo()->seoTitle(); ?></title>
<?php if( $page->seo()->hasSeoDescription() ) : ?>
    <meta name="description" content="<?php echo $page->seo()->seoDescription(); ?>">
<?php endif; ?>


You can append a prefix to the title as well. It’s good for placing the company name last in the title.

c::set('seo.prefix', ' - Some prefix');

Additional info

  • The preview has the look of Google serp.
  • The seo title and the meta description will only be cut in the preview, not on the site.
  • Newlines are stripped from the textarea and replaced with space.
  • If the seo title tag is missing, it will use the title field value instead.
  • If the meta description is missing, it will not output anything.
  • Title tag is cut, not my character, but by width, just like Google.
  • Meta description in the preview is cut after 155 characters.
  • You can click on the seo title, meta description and the seo url in the panel to edit them.

Looks nice, but shouldn’t it be

c::set('seo.postfix', ' - Some prefix');


c::set('seo.suffix', ' - Some prefix');

sind it’s appended and not prepended :wink:

Damn, I started to work on my own kirby-seo plugin a while ago, but you’re way faster than I am!

Good work, it seems we both had the same general idea about how to make the plugin. If I find improvements I’ll do some Pull Requests with some of my code or write some Issues.



I need to learn more about the word pre. :wink:

Before / after

I’ve thought more about and I don’t longer like prefix, postfix and suffix. I will try to make it simpler. In CSS before and after is used. Maybe I’ll use that, like this:

c::set('seo.title.before', 'My company: ');
c::set('seo.title.after', ' - My company');

I also added title in there so that it’s clear what it does, that it only affect the title.


I also have in mind to instead generate the title with a template, like this:

c::set('seo.title.template', '{{slug}} some text {{some_value}} {{seo_title}} - My company');

With this it could generate much more advanced titles. The problem with the above is to make it work with the live preview (javascript) without using Ajax. Could work by just dumping the content in a data field as json.

If there are these possibilities, which one do you prefer?


You can probably find improvements at this early state. The core is not that beautiful and the syntax is not completely set. It’s probably the perfect time to pop ideas, issues, feedback and pull requests. :slight_smile:

Everyone else. If you have an opinion, I would love to hear it.

UPDATE - About template

About the template thing. Maybe it’s not so good to have that as a global config because the titles may be different depending on what template is used. Instead it would need to be set in the blueprint.

    type: seo
    title: "{{slug}} some text {{some_value}} {{seo_title}} - My company"

Maybe just fallback on a config value if template is not set in the blueprint.

I think both – a global before/after text or a (local) template – can be useful. However you must decide wether the template overwrites the global before/after text or extends it (e.g. to always add the company name regardless of the template) …

If both features are implemented I think the template should always be used if it exist (because it’s more advanced). Then it will just ignore the before/after config. I don’t think it should try to combine them.

That way the template always show the full title tag which I think is most logical for users.

Because I still change my mind about things I need to think about it more. Keep ideas coming, it helps.

@tobiasweh @PaulMorel

I think I need your feedback on this.

Idea for how templates can work

This is just an seo title example and would probably not work well in real life.


    type: seo
    title: {{product_name}} has {{product_count}} products in category {{category}}

Now you may wonder what product_name, product_count and category is. It’s what you want it to be, setup by a controller.


One seo controller for each template located in:


It has a subfolder called seo. Maybe that path could be changed by an c::get option.

Controller code

This is code work like any other controller.

return function($site, $pages, $page) {
  return array(
    'product_count' => $page->children()->count(),
    'product_name' => $page->title(),
    'category' => $page->category()

Here you setup an array of the values you want to use in the seo title blueprint. They are then added as a data attribute as JSON in the panel which menas javascript can get the values.

Final words

  • I think it’s better than a snippet because you don’t use HTML, just creating an array.
  • Because you set the array you can format the values before the output. Powerful!
  • If not adding a title template in the blueprint it will fallback to the current title solution.

So, what do you think?

I think this is a really nice idea and quite flexible too. :slight_smile:

1 Like

Great stuff @jenstornell, I love this! I’ve always enjoyed this from Shopify - I can now delete this from my list of plugin ideas :wink:

1 Like

Now I’ve tested the controller much more and I forgot some things.

Here is how the controller looks now. Ok or too messy?

return function($site, $pages, $page) {
  return [
    'title' => [
      'template' => '{{product_count}} between {{category}} end',
      'values' => [
        'product_count' => $page->children()->count(),
        'product_name' => $page->title() . "'da",
        'category' => $page->category()
    'description' => [
      'template' => '{{product_count}} in description {{category}} exit',
      'values' => [
        'product_count' => $page->children()->count(),
        'product_name' => $page->title() . "'da",
        'category' => $page->category()

Title and description

In my first example I did only think of the seo title, not the description. In this version descriptions are supported as well.

Template moved from blueprint to controller

When seo title value is set I think it should fallback on the template, if it exist. Blueprints are not available on the frontend ,so I moved it to the controller.

It’s more stuff in the controller and I needed to separate template and values. On the other hand, everything is on one place.

  • Is the controller still understandable?
  • Is the controller still usable or too messy? If too messy, alternative ideas?

One idea: You could fall back to page fields if no value for a placeholder is defined. Then the category value wouldn’t need to be defined in your example.

1 Like

If I have added

to a blueprint, I get

Fatal error: Can’t use method return value in write context in H:\xampp\htdocs\kirby_seo\site\fields\seo\template.php on line 2

in the respective panel page.

Apache/2.4.9 (Win32) PHP/5.4.27, ZIP-Download from

Something is wrong.

On a mobile device now and cant test it but I am quite sure it is not compatible with your PHP version. Can add a fix for it on monday.

1 Like

@anon77445132 @tutchi

Sorry about the delay. I’m fixing some more stuff while at it.

New version released

It does NOT contain the template / controller feature this time. Sorry!

Instead it contain stuff like:

  • New syntax - Very important to change in header.php.
  • Counters - Get a counter and a warning if title or description does not fit.
  • Close button - Makes it easier to understand.
  • Old PHP version problem - Fixed, hopefully.


I’ve played around with many different syntaxes to finally found one I like. It has the same type of parameters like the snippet function.

It’s the shortest to write and yet it’s powerful.

<?php seo('title'); ?>
<?php seo('description'); ?>

That is no problem, I can wait!

But now:

I installed a new starterkit 2.2.3.
Then I copied your new files in the site-directory-structure.
I made no changes in any files!

But now I get the error:

Parse error: syntax error, unexpected ‘(string)’ (string) (T_STRING_CAST) in H:\xampp\htdocs\kirby_seo\site\plugins\seo\seo.php on line 13

if I go to any webpage or to the panel.

If I change this line 13 (using to

        if( ! $page->seo()->empty() ) { // Replace seo with custom value

I get no error!

Very Interesting, thanks.

I could not reproduce it but I’ve added a fix anyway.

I used this function instead:

It may require the latest version of Kirby. I don’t know. Can’t tell from the docs.

Hope it works for you now.

1 Like

The docs for Kirby 2.3 will most likely contain tags/markers for new features. :wink:

isNotEmpty() specifically is in Kirby since version 2.1.0.


ìsNotEmpty() was added in 2.1.0