Skip to main content

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

FieldPurpose
Enable templatesMaster switch (per-store)
Global title suffixAppended to every rendered title — e.g. | {{store.frontend_name}}
Product — Meta TitleDefault: {{product.name}}
Product — Meta DescriptionDefault: {{product.short_description}}
Category — Meta TitleDefault: {{category.name}}
Category — Meta DescriptionDefault: {{category.description}}
CMS Page — Meta TitleDefault: {{cms.title}}
CMS Page — Meta DescriptionEmpty 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 frontendApplyMetaTemplatesObserver listens on layout_generate_blocks_after and 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 frontendByte8\SeoSuite\Plugin\Catalog\ProductViewMeta is an afterPrepareAndRender plugin on Magento\Catalog\Helper\Product\View. Required because the catalog helper runs its private preparePageMetadata() after layout_generate_blocks_after fires, so the observer's value would be stomped on by core's getTitle()->set($metaTitle ?: $product->getName()). The plugin gets the last word.
  • GraphQLSeoMetadataBuilder::buildForProduct/Category/CmsPage returns 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 appends design/head/title_suffix (and prefix) to whatever value pageConfig->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's design/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