seo: SeoMetadata
The headless storefront's single source of truth for every SEO artifact a page needs.
Schema
type SeoMetadata @doc(description: "SEO metadata bundle for a single entity, designed for headless storefronts.") {
title: String @doc(description: "Rendered meta title — entity override or template.")
description: String @doc(description: "Rendered meta description.")
robots: String @doc(description: "Resolved robots directive (entity override > store default).")
canonical: String @doc(description: "Canonical absolute URL for the entity.")
open_graph: [SeoTag!]! @doc(description: "Open Graph and Twitter Card meta tags.")
structured_data: [String!]! @doc(description: "JSON-LD documents as raw JSON strings, ready to wrap in <script type=\"application/ld+json\">.")
}
type SeoTag @doc(description: "A single meta tag key/value.") {
name: String! # e.g. "og:title", "twitter:card"
content: String!
}
Available on:
ProductInterface— every product type (simple, configurable, virtual, downloadable, bundle, grouped)CategoryInterfaceCmsPageCmsPageItem
Field-by-field
title
The same string Magento's storefront would render in the <title> tag:
- If the entity has a non-empty native
meta_title→ that value verbatim - Else if meta templates are enabled and a template exists for the entity type → the rendered template (with global suffix applied)
- Else
null
The character-limit truncation that seosuite:meta:generate applies is NOT applied here — the GraphQL field returns the canonical stored/rendered value. If you want stricter truncation client-side, do it in your storefront layer.
description
Same priority chain as title but for meta_description. Returns null if neither a native value nor a template renders to non-empty.
robots
Resolved precedence:
- Entity-level
meta_robots(CMS page edit form / category edit form / product attribute) if non-empty AND not the literal stringsystem - Store config
design/search_engine_robots/default_robots null
The string system is treated as a sentinel — admins picking "System Default" from the meta_robots dropdown produce that value, which means "fall back to the store default."
canonical
- Product →
getProductUrl()(Magento core's canonical resolver) - Category →
getUrl() - CMS page →
<store_base_url>/<page_identifier>
Custom canonical mappings via byte8_seosuite_url are NOT applied at this layer — only on the storefront. If your headless storefront needs them, query the URL Relationship table directly via REST or extend the resolver.
open_graph
Flat list of {name, content} pairs. Names follow the standard og:* and twitter:* conventions. Empty array if Open Graph is disabled (byte8_seosuite/open_graph/is_active = 0).
The shape is deliberately flat (rather than a nested OpenGraph object) to map cleanly onto Next.js's Metadata.openGraph — Object.fromEntries(open_graph.map(t => [t.name, t.content])) gives you a dict.
structured_data
List of JSON-LD documents as raw JSON strings. Already escaped (</ → <\/) so safe to drop into <script type="application/ld+json">{...}</script> via dangerouslySetInnerHTML in React.
For products: includes the Product schema + Breadcrumb (when both auditors enabled). For categories: Breadcrumb only. For CMS home page: Organization + WebSite. Otherwise empty array.
Resolver classes
Each entity type has its own resolver:
| Entity | Resolver |
|---|---|
ProductInterface | Byte8\SeoSuite\Model\Resolver\Product\Seo |
CategoryInterface | Byte8\SeoSuite\Model\Resolver\Category\Seo |
CmsPage / CmsPageItem | Byte8\SeoSuite\Model\Resolver\Cms\Seo |
All three call the same stateless services:
Byte8\SeoSuite\Model\GraphQl\SeoMetadataBuilder— title, description, robots, canonicalByte8\SeoSuite\Model\GraphQl\OpenGraphBuilder— OG/Twitter tagsByte8\SeoSuite\Model\StructuredData\Builder\*— JSON-LD blocks
So the GraphQL output and the storefront output are guaranteed identical for the same entity in the same store context.
Caching
The response participates in Magento's GraphQL cache via the standard cat_p_<id>, cat_c_<id>, cms_p_<id>, cfg cache tags. Edits to the entity, store config, or SEO Suite config invalidate cleanly.
Heavy production sites should put Varnish in front and let Magento's tag-based purging handle invalidation.
Backward compatibility
The pre-v2.2 fields are preserved:
CategoryInterface.meta_robots: String— still resolved byByte8\SeoSuite\Model\Resolver\Category\MetaRobotsCmsPage.meta_robots: String— still resolved byByte8\SeoSuite\Model\Resolver\Cms\CmsPageMetaRobotsCmsPageItem.meta_robots: String— same resolver asCmsPage
Use the new seo field for everything new; the v1 fields are kept for backward compatibility but are subsumed by seo.robots.
Next
- meta_robots scalar — the v1 backward-compat field
- Examples — copy-pasteable queries for product, category, CMS