Built-in providers
Each provider is a stateless Builder class wrapped in a Registry-aware Provider. The Builder is what the GraphQL resolvers call; the Provider is what the storefront ViewModel calls.
Product (product)
Schema: https://schema.org/Product
Fires on: Product detail pages
Class: Byte8\SeoSuite\Model\StructuredData\Builder\ProductBuilder
| Field | Source | Notes |
|---|---|---|
name | getName() | |
sku | getSku() | |
description | getData('description') HTML-stripped | Skipped if empty |
image | Magento\Catalog\Helper\Image::init($product, 'product_base_image')->getUrl() | Skipped on exception |
url | getProductUrl() | |
offers | Offer sub-object: price, priceCurrency, availability, url | |
offers.availability | https://schema.org/InStock / OutOfStock / Discontinued | Disabled status maps to Discontinued |
brand | getAttributeText('brand') then getAttributeText('manufacturer') | Wrapped in Brand sub-object. Attributes are guarded — if brand doesn't exist on the catalog, falls through to manufacturer; if neither exists, omitted entirely. |
gtin | getData('gtin') | Skipped if empty |
mpn | getData('mpn') | Skipped if empty |
aggregateRating | Magento\Review\Model\AppendSummaryData::execute($product, $storeId, 'product') | Skipped if 0 reviews |
aggregateRating.ratingValue | Magento's 0–100 percentage / 20 = 0–5 scale | Rounded to 1dp |
BreadcrumbList (breadcrumb)
Schema: https://schema.org/BreadcrumbList
Fires on: Product + category pages (when there's a current category to walk)
Class: Byte8\SeoSuite\Model\StructuredData\Builder\BreadcrumbBuilder
Walks the category path (skipping root + default category, IDs ≤ 2):
{
"@type": "BreadcrumbList",
"itemListElement": [
{ "@type": "ListItem", "position": 1, "name": "Home", "item": "https://store.example/" },
{ "@type": "ListItem", "position": 2, "name": "Footwear", "item": "https://store.example/footwear" },
{ "@type": "ListItem", "position": 3, "name": "Running Shoes", "item": "https://store.example/footwear/running" },
{ "@type": "ListItem", "position": 4, "name": "Acme Pro Runner", "item": "https://store.example/acme-pro-runner.html" }
]
}
When called from a product page with no current_category in registry, the builder picks the last category ID from the product's categoryIds and walks from there.
Returns null (no schema emitted) when the trail ends up with fewer than 2 items (e.g. only Home).
Organization (organization)
Schema: https://schema.org/Organization
Fires on: cms_index_index (the home page only)
Class: Byte8\SeoSuite\Model\StructuredData\Builder\OrganizationBuilder
{
"@type": "Organization",
"name": "Sample Store Ltd",
"url": "https://store.example/",
"logo": "https://store.example/media/logo/stores/1/logo.png",
"sameAs": [
"https://www.linkedin.com/company/sample-store",
"https://twitter.com/samplestore"
]
}
| Field | Source |
|---|---|
name | Config byte8_seosuite/structured_data/org_name, falls back to Store::getName() |
url | Store::getBaseUrl() |
logo | Config byte8_seosuite/structured_data/org_logo. Absolute URLs accepted; relative paths get the base URL prepended |
sameAs | Config byte8_seosuite/structured_data/org_same_as (newline- or comma-separated) |
logo and sameAs are skipped if not configured.
WebSite + SearchAction (website)
Schema: https://schema.org/WebSite
Fires on: Home page only
Class: Byte8\SeoSuite\Model\StructuredData\Builder\WebSiteBuilder
Tells Google about your sitelinks search box:
{
"@type": "WebSite",
"name": "Sample Store",
"url": "https://store.example/",
"potentialAction": {
"@type": "SearchAction",
"target": {
"@type": "EntryPoint",
"urlTemplate": "https://store.example/catalogsearch/result/?q={search_term_string}"
},
"query-input": "required name=search_term_string"
}
}
The default urlTemplate works for stock Magento catalog search. If your storefront uses a custom search route, override via config byte8_seosuite/structured_data/search_url_template.
Next
- Custom providers — add FAQ, Article, Video, etc.