Andrew Welch
Published , updated · 5 min read · RSS Feed
Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.
Advanced SEOmatic Tips
SEOmatic is a plugin that offers deep functionality. In this article, we dive in to look at some of the more advanced features that will make your life easier, and your SEO better.
SEOmatic is a plugin for Craft CMS 3 that offers a comprehensive SEO solution for your websites. It’s easy to get up and running with it quickly, but it also has a depth of functionality when you’re ready to dive in.
In this article, I’m going to show you some more advanced things about SEOmatic that you might not know, and give you my favorite tips and tricks for using it effectively.
So let’s get going!
Link Customized Setup
If you peek into the SEOmatic plugin’s folder structure, you’ll see a directory called seomatic-config that looks pretty unassuming:
vagrant@homestead ~/sites/craft3/vendor/nystudio107/craft-seomatic (develop) $ tree -L 2 src/seomatic-config/
src/seomatic-config/
├── categorymeta
│ ├── Bundle.php
│ ├── BundleSettings.php
│ ├── GlobalVars.php
│ ├── JsonLdContainer.php
│ ├── LinkContainer.php
│ ├── ScriptContainer.php
│ ├── SitemapVars.php
│ ├── TagContainer.php
│ └── TitleContainer.php
├── entrymeta
│ ├── Bundle.php
│ ├── BundleSettings.php
│ ├── GlobalVars.php
│ ├── JsonLdContainer.php
│ ├── LinkContainer.php
│ ├── ScriptContainer.php
│ ├── SitemapVars.php
│ ├── TagContainer.php
│ └── TitleContainer.php
├── fieldmeta
│ ├── Bundle.php
│ ├── BundleSettings.php
│ ├── GlobalVars.php
│ ├── JsonLdContainer.php
│ ├── LinkContainer.php
│ ├── ScriptContainer.php
│ ├── SitemapVars.php
│ ├── TagContainer.php
│ └── TitleContainer.php
├── globalmeta
│ ├── Bundle.php
│ ├── BundleSettings.php
│ ├── Creator.php
│ ├── FrontendTemplatesContainer.php
│ ├── GlobalVars.php
│ ├── Identity.php
│ ├── JsonLdContainer.php
│ ├── LinkContainer.php
│ ├── ScriptContainer.php
│ ├── SitemapVars.php
│ ├── SiteVars.php
│ ├── TagContainer.php
│ └── TitleContainer.php
└── productmeta
├── Bundle.php
├── BundleSettings.php
├── GlobalVars.php
├── JsonLdContainer.php
├── LinkContainer.php
├── ScriptContainer.php
├── SitemapVars.php
├── TagContainer.php
└── TitleContainer.php
5 directories, 49 files
Big deal, right? Well, it kind of is… because these config files contain the blueprint for the settings that SEOmatic will use when it creates various types of meta information.
Normally this wouldn’t be that interesting, except that SEOmatic lets you override any of these built-in config files with your own!
For example, if you create many websites using SEOmatic, there may be certain things that you’d like to have setup just the way you like it, such as the Creator information (that’s you!).
So we can put something like this in our Craft config/ directory:
vagrant@homestead ~/sites/nystudio107 (develop) $ tree -L 3 config/seomatic-config/
config/seomatic-config/
└── globalmeta
└── Creator.php
1 directory, 1 file
Note that the folder structure and file name mirrors what is in the seomatic-config directory in SEOmatic itself. What SEOmatic does is it looks in the Craft config/ directory first for all of its config files, and it uses them first, rather than its own internal version!
Here’s what mine looks like:
<?php
/**
* SEOmatic plugin for Craft CMS 3.x
*
* A turnkey SEO implementation for Craft CMS that is comprehensive, powerful,
* and flexible
*
* @link https://nystudio107.com
* @copyright Copyright (c) 2017 nystudio107
*/
/**
* @author nystudio107
* @package Seomatic
* @since 3.0.0
*/
return [
'*' => [
'siteType' => 'Organization',
'siteSubType' => '',
'siteSpecificType' => '',
'computedType' => 'Organization',
'genericName' => 'nystudio107',
'genericAlternateName' => 'nys',
'genericDescription' => 'We do technology-based consulting, branding, design, and development. Making the web better one site at a time, with a focus on performance, usability & SEO',
'genericUrl' => 'https://nystudio107.com/',
'genericImage' => 'https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/img/site/nys_logo_seo.png',
'genericImageWidth' => '1042',
'genericImageHeight' => '1042',
'genericImageIds' => [],
'genericTelephone' => '',
'genericEmail' => 'info@nystudio107.com',
'genericStreetAddress' => '',
'genericAddressLocality' => 'Webster',
'genericAddressRegion' => 'NY',
'genericPostalCode' => '14580',
'genericAddressCountry' => 'US',
'genericGeoLatitude' => '43.2365384',
'genericGeoLongitude' => '-77.49211620000001',
'personGender' => '',
'personBirthPlace' => '',
'organizationDuns' => '',
'organizationFounder' => 'Andrew Welch, Polly Welch',
'organizationFoundingDate' => '2013-05-02',
'organizationFoundingLocation' => 'Webster, NY',
'organizationContactPoints' => [],
'corporationTickerSymbol' => '',
'localBusinessPriceRange' => '',
'localBusinessOpeningHours' => [],
'restaurantServesCuisine' => '',
'restaurantMenuUrl' => '',
'restaurantReservationsUrl' => '',
],
];
Boom! For any new website I create, I just have this in my boilerplate, so that the Creator information is filled in automatically for me.
You can do this with absolutely anything in SEOmatic, but I wouldn’t go too crazy: if you override any of the *Container.php files, you may not get any internal updates I make to SEOmatic that affect those files.
But if you’re willing to sync things yourself, go for it! I personally stick to the various *Vars.php files and Creator.php
N.B.: this doesn’t work like Project Config; this is just the template that is used to create new meta information. It doesn’t retroactively change any settings you’ve already made in the CP.
Link Rendering Metadata from another Route
It happens sometimes that you end up with a route that doesn’t have meta information associated with it. Perhaps you’re using a custom router, or perhaps you’re making a Google AMP version of a particular page.
Wouldn’t it be nice if there was a way you could tell SEOmatic to populate the page with meta information from another route? Well, you can!
As it turns out, SEOmatic makes a number of very useful Helper Functions available in your templates via seomatic.helper. One in particular is just what we want: seomatic.helper.loadMetadataForUri()
You pass in a URI and an optional SiteID, and it’ll load in the meta data for that URI. Here’s an example of how I use it for the AMP pages on devMode.fm, which have /amp appended to the end of them:
{% extends "_layouts/amp-layout.twig" %}
{% if entry is not defined %}
{% set entrySlug = craft.app.request.getSegment(2) %}
{% set entry = craft.entries({
"section": "episodes",
"slug": entrySlug,
}).one() %}
{% endif %}
{% do seomatic.helper.loadMetadataForUri(entry.uri) %}
{% do seomatic.script.container().include(false) %}
This populates the meta data for the Google AMP page (which doesn’t have an entry automatically injected into it) by using seomatic.helper.loadMetadataForUri(entry.uri) to load the meta information from the normal page.
This technique can be used in a variety of situations to make your life easier.
As an aside, the seomatic.script.container().include(false) statement tells SEOmatic not to include any of the script tags on this page, since third party JavaScript isn’t allowed for AMP.
Link Robust Twig API
What the previous example starts to reveal is that SEOmatic has a robust Twig Templating API that you can leverage when you really need to customize things.
The way it works is that SEOmatic takes its default settings, combines them with your Global, Content, and Entry SEO settings from the Craft CP, and injects the appropriate meta tags into containers.
SEOmatic uses the following containers:
- jsonLd — available via seomatic.jsonLd in your templates, this contains all of the JSON-LD structured data for the page
- link — available via seomatic.link in your templates, this contains all of your <link> tags
- tag — available via seomatic.tag in your templates, this contains all of your <meta> tags
- title — available via seomatic.title in your templates, this contains your <title> tag
These containers all use the same consistent api as described in the SEOmatic Tags & Containers section of the documentation, so you can do things like:
{% do seomatic.tag.get("description")
.content("Some Description")
.include(false)
%}
{% do seomatic.script.get("googleAnalytics").include(false) %}
{% do seomatic.title.get("title").content("My page title") %}
All of the containers and tags are actually PHP objects under the hood that self-validate, and allow you to manipulate them.
There’s even a self-validating object for each of the 500+ schema.org JSON-LD types! The JSON-LD Meta Object Functions section of the documentation gives you a number of examples and use-cases, such as:
{% set mainEntity = seomatic.jsonLd.get('mainEntityOfPage') %}
{% set offersJsonLd = seomatic.jsonLd.create({
'type': 'Offer',
'name': 'Some prop',
'url': 'Some url',
}, false) %}
{% do mainEntity.offers(offerJsonLd) %}
What this does is create a new Offer JSON-LD object, and adds it to the existing mainEntityOfPage JSON-LD object’s offers property.
The false parameter passed in to seomatic.jsonLd.create() tells SEOmatic not to add the JSON-LD object to the jsonLd container, because we’re adding it to a JSON-LD object that’s already in the jsonLd container. Any objects that are in the jsonLd container are automatically rendered on the page.
If you want to learn more about JSON-LD, check out the JSON-LD, Structured Data and Erotica article.
Since all of the tags and containers are self-validating models, if you have devMode on, your Yii2 Debug Toolbar will show all sorts of information. For more information on the Yii2 Debug Toolbar, check out the Profiling your Website with Craft CMS 3’s Debug Toolbar article.
I’m not going to duplicate what’s in the documentation on the Twig API, but more just make sure you realize it’s available to you!
You can use SEOmatic without ever using the Twig API, but it is there if you need to do something custom. And it allows you complete control to change whatever you like.
A really common thing to do is just to change the SEOmatic Meta Variables that are used to populate the various tags. e.g.:
{% do seomatic.meta.seoTitle(category.title) %}
Link Caching and Cache Busting
SEOmatic does a lot. It renders a ton of meta data, and since it parses all of the SEO fields in the CP as Twig templates, this can be expensive.
Because of this, SEOmatic has a two-level cache to keep things performant:
- It caches the meta containers and their tag contents before they are rendered
- It then also caches the rendered result for all of the meta containers and their tag contents
It works this way so that it can cache the containers that are made available to your Twig templates, but then also allow you to change things however you please via Twig. Thus it needs a second cache as well.
The cache used by SEOmatic is whatever you’ve set Craft to use as a Caching method, so if you clear Craft’s data caches, you’ll also be clearing SEOmatic’s caches.
Clearing the data caches something we recommend you do on deployment, as discussed in the Exploring the Craft CMS 3 Console Command Line Interface (CLI) article.
The cache SEOmatic uses is on a per-URI basis, and normally you don’t need to worry about it at all… but if you are changing tags or otherwise modifying SEOmatic meta data in a way that can be different per request for the same URI, you may need to tell the container to bust its cache.
A real-world example would be a cookie consent system where you want the user to accept certain types of cookies before you include the Google Tag Manager script. This varies from user to user for the same URI, so you’ll need to bust the cache:
{% if not consentedToCookies %}
{% do seomatic.script.get('googleTagManager').include(false) %}
{% do seomatic.script.container().clearCache(true) %}
{% endif %}
The vast majority of the time, this will not be needed, and SEOmatic will take care of the cache busting itself. You only need to use the .clearCache() method if you’re changing metadata via Twig and it can change from request to request for the same URI.
Additionally, as of SEOmatic 3.4.9, there is an allowedUrlParams setting that allows you to add URL parameters that will be used as part of the cache key. Normally URL params are stripped from the URI when determining the cache key.
So for instance, if you added foo to the allowedUrlParams array, /woof?foo=bar and /woof?foo=baz, etc. would each be unique SEOmatic cache keys.
One other quick caching tip: if you’re using Craft in a load-balanced environment, make sure that your cache is coherent across the various servers!
Link A Peek Under the Hood
I mentioned earlier that SEOmatic puts everything into containers, and that all of your tags are actually PHP objects. But let’s look at what this actually means.
So let’s dump the tag container and see what’s inside:
{{ dump(seomatic.tag.container()) }}
You’ll see an output that will look something like this:
/home/vagrant/sites/craft3/vendor/twig/twig/src/Extension/DebugExtension.php:61:
object(nystudio107\seomatic\models\MetaTagContainer)[3727]
public 'data' =>
array (size=5)
'generator' =>
object(nystudio107\seomatic\models\MetaTag)[3711]
public 'charset' => string '' (length=0)
public 'content' => string 'SEOmatic' (length=8)
public 'httpEquiv' => string '' (length=0)
public 'name' => string 'generator' (length=9)
public 'property' => null
private '_errors' (yii\base\Model) => null
private '_validators' (yii\base\Model) => null
private '_scenario' (yii\base\Model) => string 'default' (length=7)
private '_events' (yii\base\Component) =>
array (size=0)
...
private '_eventWildcards' (yii\base\Component) =>
array (size=0)
...
private '_behaviors' (yii\base\Component) =>
array (size=0)
...
public 'include' => boolean true
public 'key' => string 'generator' (length=9)
public 'environment' => null
public 'dependencies' =>
array (size=1)
...
'keywords' =>
object(nystudio107\seomatic\models\MetaTag)[3478]
public 'charset' => string '' (length=0)
public 'content' => string '{seomatic.meta.seoKeywords}' (length=27)
public 'httpEquiv' => string '' (length=0)
public 'name' => string 'keywords' (length=8)
public 'property' => null
private '_errors' (yii\base\Model) => null
private '_validators' (yii\base\Model) => null
private '_scenario' (yii\base\Model) => string 'default' (length=7)
private '_events' (yii\base\Component) =>
array (size=0)
...
private '_eventWildcards' (yii\base\Component) =>
array (size=0)
...
private '_behaviors' (yii\base\Component) =>
array (size=0)
...
public 'include' => boolean true
public 'key' => string 'keywords' (length=8)
public 'environment' => null
public 'dependencies' => null
'description' =>
object(nystudio107\seomatic\models\metatag\DescriptionTag)[3477]
public 'charset' => string '' (length=0)
public 'content' => string '{seomatic.meta.seoDescription}' (length=30)
public 'httpEquiv' => string '' (length=0)
public 'name' => string 'description' (length=11)
public 'property' => null
private '_errors' (yii\base\Model) => null
private '_validators' (yii\base\Model) => null
private '_scenario' (yii\base\Model) => string 'default' (length=7)
private '_events' (yii\base\Component) =>
array (size=0)
...
private '_eventWildcards' (yii\base\Component) =>
array (size=0)
...
private '_behaviors' (yii\base\Component) =>
array (size=0)
...
public 'include' => boolean true
public 'key' => string 'description' (length=11)
public 'environment' => null
public 'dependencies' => null
'referrer' =>
object(nystudio107\seomatic\models\metatag\ReferrerTag)[3475]
public 'charset' => string '' (length=0)
public 'content' => string 'no-referrer-when-downgrade' (length=26)
public 'httpEquiv' => string '' (length=0)
public 'name' => string 'referrer' (length=8)
public 'property' => null
private '_errors' (yii\base\Model) => null
private '_validators' (yii\base\Model) => null
private '_scenario' (yii\base\Model) => string 'default' (length=7)
private '_events' (yii\base\Component) =>
array (size=0)
...
private '_eventWildcards' (yii\base\Component) =>
array (size=0)
...
private '_behaviors' (yii\base\Component) =>
array (size=0)
...
public 'include' => boolean true
public 'key' => string 'referrer' (length=8)
public 'environment' => null
public 'dependencies' => null
'robots' =>
object(nystudio107\seomatic\models\metatag\RobotsTag)[3474]
public 'charset' => string '' (length=0)
public 'content' => string 'none' (length=4)
public 'httpEquiv' => string '' (length=0)
public 'name' => string 'robots' (length=6)
public 'property' => null
private '_errors' (yii\base\Model) => null
private '_validators' (yii\base\Model) => null
private '_scenario' (yii\base\Model) => string 'default' (length=7)
private '_events' (yii\base\Component) =>
array (size=0)
...
private '_eventWildcards' (yii\base\Component) =>
array (size=0)
...
private '_behaviors' (yii\base\Component) =>
array (size=0)
...
public 'include' => boolean true
public 'key' => string 'robots' (length=6)
public 'environment' =>
array (size=3)
...
public 'dependencies' => null
private '_errors' (yii\base\Model) => null
private '_validators' (yii\base\Model) => null
private '_scenario' (yii\base\Model) => string 'default' (length=7)
private '_events' (yii\base\Component) =>
array (size=0)
empty
private '_eventWildcards' (yii\base\Component) =>
array (size=0)
empty
private '_behaviors' (yii\base\Component) =>
array (size=0)
empty
public 'name' => string 'General' (length=7)
public 'description' => string 'General Meta Tags' (length=17)
public 'class' => string 'nystudio107\seomatic\models\MetaTagContainer' (length=44)
public 'handle' => string 'general' (length=7)
public 'include' => boolean true
public 'dependencies' =>
array (size=0)
empty
public 'clearCache' => boolean false
We can see our container, with each <meta> tag represented as an object that has properties that we can modify as we see fit. We can add tags, remove tags… the sky’s the limit!
It works the same way for all of the other containers as well.
As mentioned earlier, all of the 500+ schema.org types are available as objects in SEOmatic for your structured data pleasure.
There’s a special mainEntityOfPage JSON-LD object that SEOmatic injects on each page that describes the thing the page is about.
{% set mainEntity = seomatic.jsonLd.get('mainEntityOfPage') %}
{{ dump(mainEntity) }}
Let’s have a look at one where it’s set to IndividualProduct:
object(nystudio107\seomatic\models\jsonld\IndividualProduct)[3647]
public 'serialNumber' => null
public 'additionalProperty' => null
public 'aggregateRating' => null
public 'audience' => null
public 'award' => null
public 'brand' => null
public 'category' => null
public 'color' => null
public 'depth' => null
public 'gtin12' => null
public 'gtin13' => null
public 'gtin14' => null
public 'gtin8' => null
public 'height' => null
public 'isAccessoryOrSparePartFor' => null
public 'isConsumableFor' => null
public 'isRelatedTo' => null
public 'isSimilarTo' => null
public 'itemCondition' => null
public 'logo' => null
public 'manufacturer' => null
public 'material' => null
public 'model' => null
public 'mpn' => null
public 'offers' => null
public 'productID' => null
public 'productionDate' => null
public 'purchaseDate' => null
public 'releaseDate' => null
public 'review' => null
public 'sku' => null
public 'weight' => null
public 'width' => null
public 'additionalType' => null
public 'alternateName' => null
public 'description' => string '{seomatic.meta.seoDescription}' (length=30)
public 'disambiguatingDescription' => null
public 'identifier' => null
public 'image' =>
array (size=2)
'type' => string 'ImageObject' (length=11)
'url' => string '{seomatic.meta.seoImage}' (length=24)
public 'mainEntityOfPage' => string '{seomatic.meta.canonicalUrl}' (length=28)
public 'name' => string '{seomatic.meta.seoTitle}' (length=24)
public 'potentialAction' =>
array (size=3)
'type' => string 'SearchAction' (length=12)
'target' => string '{seomatic.site.siteLinksSearchTarget}' (length=37)
'query-input' => string '{seomatic.helper.siteLinksQueryInput()}' (length=39)
public 'sameAs' => null
public 'url' => string '{seomatic.meta.canonicalUrl}' (length=28)
public 'context' => string 'http://schema.org' (length=17)
public 'type' => string '{seomatic.meta.mainEntityOfPage}' (length=32)
public 'id' => null
private '_errors' (yii\base\Model) => null
private '_validators' (yii\base\Model) => null
private '_scenario' (yii\base\Model) => string 'default' (length=7)
private '_events' (yii\base\Component) =>
array (size=0)
empty
private '_eventWildcards' (yii\base\Component) =>
array (size=0)
empty
private '_behaviors' (yii\base\Component) =>
array (size=0)
empty
public 'include' => boolean true
public 'key' => string 'mainEntityOfPage' (length=16)
public 'environment' => null
public 'dependencies' => null
As you can see, this is a PHP object as well, with all of the properties that the IndividualProduct schema.org type has! You can manipulate these properties to your heart’s content.
My recommendation when it comes to structured data is that you focus on the types that Google consumes, as per the Structured Data Search Gallery. Give a listen to the devMode.fm podcast SEO for Web Developers: The Silence of the Chickens for more on this.
Link Wrapping up
SEOmatic supports a whole host of things we didn’t cover here, such as multi-site & multi-lingual support, Google AMP support, fine-grained permissions for each CP function, “headless Craft” SPA support, pagination and oodles more. Check out the SEOmatic Documentation if you want more on those!
But hopefully these tips presented here help you understand how to use SEOmatic a bit more effectively!