Meta Tags Plugin

plugin
seo
opengraph
twittercard

#1

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.


How can I echo the url of the image from a page or posts in the header for twittercard or opengraph?
#2

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.


#3

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.


Upgrade to 2.5.6/2.5.7: Cannot declare class Asset, because the name is already in use
#4

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'),
        ]
    ];
});

#5

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.

#6

@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!


#7

@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?


#8

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.


Strip comma from last element in JSON string for Schema Markup
#9

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


#10

@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