Meta templates overview
Define one template per entity type, drop variables like {{product.name}} into it, and the SEO Suite renders a unique meta title/description for every product/category/CMS page automatically. Native overrides (per-product meta_title set by an editor) always win, so this never disturbs hand-written copy.
Why use it
Magento ships with no template engine for meta. Without one you either:
- Leave meta empty → Google generates one from the product description (often awkward), or
- Write meta_title/meta_description by hand for every SKU → a full-time job for catalogs above ~500 products
Templates give you 80% of the benefit of hand-written meta with 1% of the effort, and keep editors free to override the 20% of high-traffic pages that deserve special copy.
Configuration
Stores → Configuration → SEO Suite → Meta Title & Description Templates
| Field | Purpose |
|---|---|
| Enable templates | Master switch (per-store) |
| Global title suffix | Appended to every rendered title — e.g. | {{store.frontend_name}} |
| Product — Meta Title | Default: {{product.name}} |
| Product — Meta Description | Default: {{product.short_description}} |
| Category — Meta Title | Default: {{category.name}} |
| Category — Meta Description | Default: {{category.description}} |
| CMS Page — Meta Title | Default: {{cms.title}} |
| CMS Page — Meta Description | Empty by default |
Per-store overrides supported.
How rendering works
All paths share the same engine (Byte8\SeoSuite\Model\MetaTemplate\TemplateRenderer) and the same context applicator (Byte8\SeoSuite\Model\MetaTemplate\PageMetaApplicator):
- Category / CMS frontend —
ApplyMetaTemplatesObserverlistens onlayout_generate_blocks_afterand calls the applicator. Category and CMS blocks set their titles during_prepareLayout(which fires before the event), so the observer's override runs after them and wins. - Product frontend —
Byte8\SeoSuite\Plugin\Catalog\ProductViewMetais anafterPrepareAndRenderplugin onMagento\Catalog\Helper\Product\View. Required because the catalog helper runs its privatepreparePageMetadata()afterlayout_generate_blocks_afterfires, so the observer's value would be stomped on by core'sgetTitle()->set($metaTitle ?: $product->getName()). The plugin gets the last word. - GraphQL —
SeoMetadataBuilder::buildForProduct/Category/CmsPagereturns the rendered values for headless storefronts.
The renderer is stateless and passes context via dependency injection — no Registry magic in the GraphQL path.
Note on Magento's
design/head/title_suffix— Magento core always appendsdesign/head/title_suffix(and prefix) to whatever valuepageConfig->getTitle()->set()receives. If you've set both Magento's title suffix and the SEO Suite suffix you'll see the store name twice in the output (<title>Foo | Store Name Store Name</title>). Either clear Magento'sdesign/head/title_suffix(Stores → Configuration → General → Design → HTML Head) or leave the SEO Suite suffix empty.
What the suffix does
The suffix is a separate template that's rendered with the same context and appended to the title:
- Title template:
{{product.name}} - Suffix template:
| {{store.frontend_name}} - Final:
Acme Widget Pro | Sample Store
If the suffix renders empty (e.g. store.frontend_name is blank), the engine collapses leftover separators so you don't end up with a dangling |.
Empty-variable handling
If a variable resolves to empty, the template engine collapses surrounding separators (|, ·, •, -, –, —, :) so output stays tidy. Example with product.brand empty:
- Template:
{{product.name}} | {{product.brand}} | {{store.frontend_name}} - Renders:
Acme Widget Pro | Sample Store(the empty middle is removed cleanly)
Native overrides
If a product has meta_title = "Buy the Acme Widget Pro 2026 — 30-day returns", the template engine leaves it alone. Same for category meta_description, CMS page meta_title / meta_description.
This is enforced at observer level — the observer reads product.getData('meta_title') and skips the entity if it's non-empty. There's no way to force the template to override a hand-written meta_title (use the AI Meta Generator or manually clear the field if you want a re-templating).
Next
- Variables reference — every variable available in every namespace
- Extending — adding your own variable namespace via
VariableResolverInterface