Meta Tags Plugin

HTML meta tags generator for Kirby. Supports Open Graph and Twitter Cards out of the box.

$ kirby plugin:install pedroborges/kirby-meta-tags

Basic Usage

After installing the Meta Tags plugin, you will need to add one line to the head element on your template, or header.php snippet:

<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <?php echo $page->metaTags() ?>

Default

The plugin ships with some default meta tags enabled for your convenience:

c::set('meta-tags.default', [
    'title' => site()->title(),
    'meta' => [
        'description' => site()->description()
    ],
    'link' => [
        'canonical' => page()->url()
    ],
    'og' => [
        'title' => page()->title(),
        'type' => 'website',
        'site_name' => site()->title(),
        'url' => page()->url()
    ]
]);

The meta-tags.default option is applied to all pages on your Kirby site. Of course you can and I encourage you to change these defaults. In order to do that, you just need to copy this example to your site/config/config.php and tweak it to fit your needs.

Templates

Following the flexible spirit of Kirby, you also have the option to add template specific meta tags:

c::set('meta-tags.templates', [
    'article' => [ // template name
        'og' => [  // tags group name
            'type' => 'article', // overrides the default
            'namespace:article' => function($page) {
                return [
                    'author' => $page->author(),
                    'published_time' => $page->date('%F'),
                    'modified_time' => $page->modified('%F'),
                    'tag' => ['tech', 'web']
                ];
            },
            'namespace:image' => function($page) {
                $image = $page->cover()->toFile();

                return [
                    'image' => $image->url(),
                    'width' => $image->width(),
                    'height' => $image->height(),
                    'type' => $image->mime()
                ];
            }
        ]
    ],
]);

When a template key matches the current page’s template name, it is merged and overrides any repeating properties defined on the meta-tags.default option so you don’t have to repeat yourself.

Result HTML

<!-- merged default definition -->
<title>Pedro Borges</title>
<meta name="description" content="My personal website">
<meta property="og:title" content="My blog post title">
<meta property="og:site_name" content="Pedro Borges">
<meta property="og:url" content="https://pedroborg.es/blog/my-article">
<!-- template definition -->
<meta property="og:type" content="article">
<meta property="og:article:author" content="Pedro Borges">
<meta property="og:article:published_time" content="2017-02-28">
<meta property="og:article:modified_time" content="2017-03-01">
<meta property="og:article:tag" content="tech">
<meta property="og:article:tag" content="web">
<meta property="og:image" content="https://pedroborg.es/content/blog/my-article/cover.jpg">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:type" content="image/jpeg">
<link rel="canonical" href="https://pedroborg.es/blog/my-article">

There are a lot of more examples on the repo.

9 Likes

Can I create custom meta tags and links? If yes, how do I go about doing that? Could you provide an example. Something like a humans.txt or a google notranslate.

Release v1.0.0

The previous version has been stable enough and no issue has been reported in 8 months :smiley:

I finally got time to make some improvements and update the documentation so it’s time to release v1.0.0.

Changed

  • Options meta-tags.default and meta-tags.templates can now return a closure which receives $page and $site as arguments:

    c::set('meta-tags.default', function(Page $page, Site $site) {
        return [
            'title' => $site->title(),
            'meta' => [
                'description' => $site->description()
            ],
            'link' => [
                'canonical' => $page->url()
            ],
            'og' => [
                'title' => $page->isHomePage()
                    ? $site->title()
                    : $page->title(),
                'type' => 'website',
                'site_name' => $site->title(),
                'url' => $page->url()
            ]
        ];
    });
    
  • Closures in other places now receive $site and second argument as well:

    'link' => [
        'canonical' => $page->url(),
        'alternate' => function(Page $page, Site $site) {
            $locales = [];
    
            foreach ($site->languages() as $language) {
                if ($language->isDefault()) continue;
    
                $locales[] = [
                    'hreflang' => $language->code(),
                    'href' => $page->url($language->code())
                ];
            }
    
            return $locales;
        }
    ],
    

Besides offering a better workflow, these changes also help avoid an issue where site() can’t be called outside a closure from the config file in multi-language websites, as reported on the Kirby repository.

Hi @kinkwilde, sorry for missing your question a few months ago. You may have found the answer already but I want to share an example from a project I’m working on because it may help others, as well.

c::set('meta-tags.default', function(Page $page, Site $site) {
    $name = $site->name();
    $title = $page->isHomePage()
                ? $site->title().' / '.$name
                : $page->title().' / '.$name;
    $description = $page->isHomePage()
                ? $site->description()->html()
                : $page->description()->html();

    return [
        'title' => $title,
        'meta' => [
            'description' => $description,
            'application-name' => $name->html(),
            'apple-mobile-web-app-title' => $name->html(),
            'apple-mobile-web-app-status-bar-style' => '#6767ad',
        ],
        'link' => [
            'canonical' => $page->url(),
            'icon' => [
                ['href' => url('assets/images/icons/favicon-16.png'), 'sizes' => '16x16', 'type' =>'image/png'],
                ['href' => url('assets/images/icons/favicon-32.png'), 'sizes' => '32x32', 'type' =>'image/png'],
                ['href' => url('assets/images/icons/favicon-96.png'), 'sizes' => '96x96', 'type' =>'image/png']
            ],
            'apple-touch-icon' => [
                ['href' => url('assets/images/icons/apple-touch-icon-152.png'), 'sizes' => '152x152', 'type' =>'image/png'],
                ['href' => url('assets/images/icons/apple-touch-icon-167.png'), 'sizes' => '167x167', 'type' =>'image/png'],
                ['href' => url('assets/images/icons/apple-touch-icon-180.png'), 'sizes' => '180x180', 'type' =>'image/png'],
            ],
            'stylesheet' => [
                ['href' => url('assets/css/main.css')]
            ],
        ],
        'og' => [
            'site_name' => $name->html(),
            'url' => $page->url(),
            'type' => 'website',
            'title' => $page->isHomePage()
                ? $site->title()
                : $page->title(),
            'description' => $description,
            'image' => url('assets/images/cover.png'),
        ],
        'twitter' => [
            'site' => $site->twitter(),
            'card' => 'summary_large_image',
            'title' => $page->isHomePage()
                ? $site->title()
                : $page->title(),
            'description' => $description,
            'image' => url('assets/images/cover.png'),
        ]
    ];
});

Release v1.1.0

This release adds support for JSON-LD schemas and a few other improvements, check it out on Github!

Added

  • Support for JSON-LD schema:

    'json-ld' => [
        'Organization' => [
            'name' => $site->title()->value(),
            'url' => $site->url(),
            "contactPoint" => [
                '@type' => 'ContactPoint',
                'telephone' => $site->phoneNumber()->value(),
                'contactType' => 'customer service'
            ]
        ]
    ]
    

    Output:

    <script type="application/ld+json">
    {
        "@context": "http://schema.org",
        "@type": "Organization",
        "name": "Example Co",
        "url": "https://example.com",
        "contactPoint": {
            "@type": "ContactPoint",
            "telephone": "+1-401-555-1212",
            "contactType": "customer service"
        }
    }
    </script>
    
  • Support for rendering one tag or a specific group of tags:

    <?php echo $page->metaTags('title') ?>
    
    // or passing an array
    
    <?php echo $page->metaTags(['og', 'twitter', 'json-ld']) ?>
    

Fixed

  • Tags with empty value being rendered with invalid markup.
1 Like

@pedroborges This is a pretty big update! Developers might not immediately realise that JSON-LD can be used to improve SEO rankings. To help educate them, you may want to point them to this page in the Google Developers website.

Great work!

1 Like

@pedroborges This is fantastic! Well done. I can finally ditch my set of manually created set of schema snippets. This is the best Sunday morning ever. Does it support breadcrumb schema?

1 Like

Thanks for the love guys!

@jimbobrjames you can use schema available at schema.org, just pass a multidimensional array to json-ld and the plugin will take care of the rest, like so:

c::set('meta-tags.default', function ($page, $site) {
    $pages = $site->pages()->visible();
    $breadcrumb = [];

    foreach ($pages as $p) {
        $breadcrumb[] = [
            '@type'    => 'ListItem',
            'position' => $p->num(),
            'item'     => [
                '@id'  => $p->url(),
                'name' => $p->title()->value(),
            ],
        ];
    }

    return [
        'title'   => $page->title(),
        'meta'    => [/* ... */],
        'link'    => [/* ... */],
        'og'      => [/* ... */],
        'twitter' => [/* ... */],
        'json-ld' => [
            'BreadcrumbList' => [
                'itemListElement' => $breadcrumb
            ]
        ]
    ];
});

The output when you call $page->metaTags() will be:

<script type="application/ld+json">
{
    "@context": "http://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
        {
            "@type": "ListItem",
            "position": "1",
            "item": {
                "@id": "https://example.com",
                "name": "Homepage"
            }
        },
        {
            "@type": "ListItem",
            "position": "2",
            "item": {
                "@id": "https://tbm.test/about",
                "name": "About Us"
            }
        }
    ]
}
</script>

The first level @context and @type can be omitted and the plugin will include them for you, but you have to include the @type property for nested items.

1 Like

Just found a small bug and pushed v1.1.1 to fix it. Update the plugin :wink:

@pedroborges:

Thanks for your Kirby Meta Tags plugin. To test it I have downloaded a new Kirby Starterkit 2.5.12, installed it, then added a new snippet site/snippets/breadcrumb.php with the source like Kirby docs: Breadcrumb menu, added the needed css for this and added <?php snippet('breadcrumb') ?> in the last line of the snippet site/snippets/header.php.

After this I installed your Kirby Meta Tags plugin, added a new line with <?php echo $page->metaTags() ?> in the “head” of the snippet site/snippets/header.php and added your config to my site/config/config.php.

All works and I get NO error. Thanks.

But I have read https://schema.org/BreadcrumbList, particularly the first two paragraphs.

If I look at the source code e.g. of my page “projects/project-c” I find a difference between the links of the Breadcrumb menu and the links in your BreadcrumbList. Also the numbering is not correct e.g. if the (blog) pages are sorted by date.

Therefore I do not consider your configuration to be correct with the meaning of https://schema.org/BreadcrumbList and suggest the following start of your configuration :

c::set('meta-tags.default', function ($page, $site) {
    $pages = $site->breadcrumb();
    $nn = 0;
    $breadcrumb = [];

    foreach ($pages as $p) {
        $breadcrumb[] = [
            '@type'    => 'ListItem',
            'position' => ++$nn,
            'item'     => [
                '@id'  => $p->url(),
                'name' => $p->title()->value(),
            ],
        ];
    }

I am pleased about your feedback.

HeinerEF

I am wondering the following: is the plugin generating automatic some info? For example the meta-description. Or do you have to make a blueprint for the meta description. Is it even possible to influence some info from the plugin with a blueprint?

@portobien The plugin uses default fields like the title or a $site->description() field, but you can adapt all that in the config settings, see the readme for details.

Depending on what fields you have set up, it might make sense to add dedicated fields for this purpose.

You could use my Chopper plugin, which will get a set amount of text from a field based on either a word or carachter count. This is useful for descriptions. I usually use it as a fallback for if the dedicated meta description field is empty.

1 Like

Okay! But if you don’t change this then every page on the website has the same meta description? Am I right?

So could I change maybe the $site->description() to something like $page->metadescription() ?

Thanks sounds good! For Kirby 2 there where great (user input / visual) SEO plugins. I try to make one voor K3!

Yes, looks like the default is to apply the same stuff to every page. All the page specific stuff is done via the options.

1 Like

I’ve developed one for Kirby 3 already, that I suggest you to check out and help improve, before developing your own. If it fits you, we might avoid having two plugins with the same purpose :wink:

See my post in the other thread:
https://forum.getkirby.com/t/seo-for-kirby-3/13228/7?u=reh

1 Like

Thanks!