Centralised SEO for SilverShop (CMS 6): per-page meta (canonical, robots, OpenGraph, Twitter), one coherent JSON-LD @graph assembled from pluggable structured-data providers, plus an XML sitemap and robots.txt.
Package info
github.com/silvershop-suite/silvershop-seo
Type:silverstripe-vendormodule
pkg:composer/silvershop-suite/seo
Requires
- php: ^8.3
- silvershop/core: ^6
- silverstripe/cms: ^6.0
- silverstripe/framework: ^6.0
- wilr/silverstripe-googlesitemaps: ^4
Requires (Dev)
Suggests
- silvershop-suite/brands: Adds the product brand to the Product node.
- silvershop-suite/product-gallery: Uses the full product/variation gallery as the Product image[] and social-image source.
- silvershop-suite/reviews: Adds aggregate rating + reviews to the Product node and the shop rating to the Organization node.
- silvershop-suite/search: Adds a WebSite SearchAction (sitelinks search box) pointing at the search page.
- silvershop-suite/store-profile: Supplies the Organization / PostalAddress / ContactPoint data and the logo social-image fallback.
- silvershop/stock: Sets the Offer availability (InStock / OutOfStock) from live stock levels.
- silverstripe/blog: Activates the Article / BlogPosting structured-data provider for blog posts.
- symbiote/silverstripe-queuedjobs: Schedules a background full-sitemap rebuild on large catalogs.
- tractorcow/silverstripe-fluent: Emits hreflang alternates and locale-correct canonicals when multilingual.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-19 19:54:55 UTC
README
Centralised SEO for SilverShop (SilverStripe CMS 6). It makes SEO a first-class, installable layer instead of markup baked into the theme or scattered across feature modules. It owns:
- Per-page meta — canonical, meta robots (index/follow), OpenGraph and Twitter cards, with sensible auto-defaults and a social-image fallback chain.
- One JSON-LD
@graph— a single, coherent structured-data document assembled from pluggable providers (Product/Offer, Organization/WebSite, BreadcrumbList, category ItemList), enriched from other modules (reviews, brands, …) without editing them. - Sitemap + robots.txt — an XML sitemap (built on
wilr/silverstripe-googlesitemaps) and a generatedrobots.txt.
It builds on the CMS 6 updateMetaComponents hook, so a theme only needs the standard $MetaTags in its <head> —
nothing else to wire up.
Requirements
- PHP 8.3+
silverstripe/framework^6,silverstripe/cms^6silvershop/core^6wilr/silverstripe-googlesitemaps^4 (the sitemap spine)
Installation
composer require silvershop-suite/seo
Then run a dev/build (/dev/build?flush=1). The extensions are applied automatically to SiteTree and SiteConfig;
a new SEO tab appears on every page and in Settings.
How it works
Meta (the SEO tab)
Every page gains an SEO tab with:
- Meta title — overrides
<title>; blank falls back to the page title. - Meta description — the core field, moved here for one place to edit search snippets.
- Canonical URL override — blank = the page's own absolute URL (self-referential canonical).
- Search engine indexing —
index/followtoggles. Cart, checkout, account and search pages are alwaysnoindexregardless (seenoindex_page_classes), andnoindexpages are excluded from the sitemap so the sitemap and robots agree. - Social sharing — OpenGraph + Twitter title/description/image overrides and the Twitter card type.
Site-wide defaults live in Settings → SEO: a default share image, the Twitter site handle, the organization's
social-profile URLs (sameAs), and an optional hand-written robots.txt.
The social-image fallback chain is: page OG image → the product's primary / gallery image → SiteConfig default share image → store-profile logo.
All of this is emitted through SiteTree::updateMetaComponents() as structured tag descriptors, so output is clean,
de-duplicated and theme-independent.
Structured data — the StructuredDataProvider contract
The JSON-LD @graph is assembled by JsonLdGraphService from the providers registered in
StructuredDataRegistry. Each provider implements:
interface StructuredDataProvider { public function getName(): string; public function appliesTo(SeoContext $context): bool; // right page/record type, module present, data non-empty public function getNodes(SeoContext $context): array; // schema.org nodes, or @id-keyed fragments to merge }
Nodes that share an @id are merged, so an enrichment fragment (e.g. an aggregateRating) lands inside the node
another provider created rather than producing a second, competing node. SeoContext mints the shared @ids
(…/#product, …/#organization, …/#website, …/#breadcrumbs) so every provider agrees.
Register your own provider in config:
SilverShop\Seo\StructuredData\StructuredDataRegistry: providers: my-thing: '%$My\Module\MyProvider'
Bundled providers
| Provider | Nodes / fragments | Active when |
|---|---|---|
OrganizationProvider |
Organization + PostalAddress + WebSite (+ SearchAction) |
always (store-profile data when present, else SiteConfig title) |
ProductProvider |
Product + Offer (price, currency, availability, SKU, condition, seller) + BreadcrumbList |
on SilverShop\Page\Product |
ListingProvider |
ItemList + BreadcrumbList |
on SilverShop\Page\ProductCategory |
ReviewsAdapterProvider |
aggregateRating + review[] → Product node; shop aggregateRating → Organization node |
silvershop-suite/reviews installed |
BrandAdapterProvider |
brand → Product node |
silvershop-suite/brands installed, product has a brand |
ArticleProvider |
BlogPosting |
silverstripe/blog installed |
EventProvider |
Event |
an events page class is listed in event_classes |
The reviews, brands, blog and events integrations are seo-side adapters: they read the other module's data behind
a class_exists guard and contribute to the graph without editing that module. They are dormant (and emit
nothing) when the module is absent. Offer availability (InStock / OutOfStock) is read from silvershop/stock
when present.
Known caveat — reviews double-emit
silvershop-suite/reviews still prints its own standalone <script type="application/ld+json"> on product and shop pages.
With both modules installed, a page therefore emits the rating twice: once inside this module's @graph, once in
the reviews module's own block. Google tolerates duplicate aggregateRating, but the clean fix is a coordinated
reviews release in which reviews implements this contract and self-suppresses its standalone markup when
silvershop-suite/seo is present. That is a documented follow-up; this module does not (and must not) edit reviews.
Sitemap
The XML sitemap is served at /sitemap.xml by wilr/silverstripe-googlesitemaps, which already includes every
published page — Products, ProductCategories, brand pages and content pages are all SiteTree, so they are covered
out of the box (respecting ShowInSearch), and each page's primary image is included. This module additionally
excludes noindex pages so the sitemap matches the robots directives.
robots.txt
/robots.txt is served by RobotsController. By default it references the sitemap and disallows the cart, checkout,
account and search pages (resolved from their live URLs). A merchant can supply a full custom body in
Settings → SEO → Custom robots.txt.
SilverShop\Seo\Control\RobotsController: include_sitemap: true sitemap_path: 'sitemap.xml' disallow: - '/dev'
Configuration reference
SilverShop\Seo\Extension\SeoSiteTreeExtension: og_type_default: 'website' noindex_page_classes: - 'SilverShop\Page\CartPage' - 'SilverShop\Page\CheckoutPage' - 'SilverShop\Page\AccountPage' - 'SilverShop\Search\SearchPage' SilverShop\Seo\StructuredData\Provider\OrganizationProvider: organization_type: 'Organization' # or LocalBusiness / Store for a physical storefront SilverShop\Seo\StructuredData\Provider\ProductProvider: item_condition: 'https://schema.org/NewCondition' price_valid_period: '+1 year' # strtotime() expression for Offer priceValidUntil; blank = omit image_limit: 6 SilverShop\Seo\StructuredData\Provider\EventProvider: event_classes: - 'My\Events\EventPage'
Multi-locale (hreflang)
tractorcow/silverstripe-fluent is not required. hreflang alternates and locale-correct canonicals are intended as a
guarded, forward-looking addition that activates when Fluent is installed.
Theme note
Per the storefront direction, the theme renders no JSON-LD and defers to this module: it only needs the standard
$MetaTags in <head>. Core's inline microdata can stay as a no-JS fallback; the JSON-LD @graph is the single
source of truth where they overlap.
License
BSD-3-Clause.
Credits
Built and maintained by Remy Vaartjes (remy@xd.nl) — part of the SilverShop-Suite.