Skip to main content

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

FieldSourceNotes
namegetName()
skugetSku()
descriptiongetData('description') HTML-strippedSkipped if empty
imageMagento\Catalog\Helper\Image::init($product, 'product_base_image')->getUrl()Skipped on exception
urlgetProductUrl()
offersOffer sub-object: price, priceCurrency, availability, url
offers.availabilityhttps://schema.org/InStock / OutOfStock / DiscontinuedDisabled status maps to Discontinued
brandgetAttributeText('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.
gtingetData('gtin')Skipped if empty
mpngetData('mpn')Skipped if empty
aggregateRatingMagento\Review\Model\AppendSummaryData::execute($product, $storeId, 'product')Skipped if 0 reviews
aggregateRating.ratingValueMagento's 0–100 percentage / 20 = 0–5 scaleRounded to 1dp

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"
]
}
FieldSource
nameConfig byte8_seosuite/structured_data/org_name, falls back to Store::getName()
urlStore::getBaseUrl()
logoConfig byte8_seosuite/structured_data/org_logo. Absolute URLs accepted; relative paths get the base URL prepended
sameAsConfig 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