One Block, One Graph, Zero Duplicates
Own blocks are explicitly skipped — scripts carrying data-panth-seo are preserved.
One JSON-LD block per page, product-type aware, theme-agnostic. Panth Structured Data emits a single with a deduplicated @graph covering every schema.org node Google cares about — Product, Offer / Aggr…
Key Features:
Additional Services
Built-in from day one. No add-ons, no upsell, no licence keys to renew.
Own blocks are explicitly skipped — scripts carrying data-panth-seo are preserved.
Simple → Product + scalar Offer (price / availability / itemCondition / seller / priceValidUntil / shippingDetails).
Organization — name / url / logo / legalName / contactPoint (phone + email) / PostalAddress / sameAs array merging the 7 dedicated social…
BreadcrumbList — full category hierarchy for products (Home → Gear → Bags → Product), not just Home + Product. Respects admin-configured…
MerchantReturnPolicy — applicableCountry (ISO 3166-1), merchantReturnDays (int), returnMethod, returnFees (properly mapped to the schema.…
Brand — standalone Brand node using the configured product attribute (default manufacturer) + admin-configurable default fallback brand.
Panth Structured Data is a JSON-LD schema markup extension for Magento 2 and Adobe Commerce that emits one validated rich-result graph per page.
The module replaces the scattered structured data Magento ships natively with a single deduplicated application/ld+json block per URL. An Aggregator deep-merges every contributor (Product, AggregateOffer, BreadcrumbList, Organization, WebSite, FAQPage) by @id, then a native-markup strip plugin removes the duplicate JSON-LD that Magento's product, breadcrumb, and price blocks otherwise inject — the source of most Google Search Console "duplicate field" errors. Existing blocks carrying data-panth-seo are preserved automatically.
The module covers all six Magento product types — simple, configurable, bundle, grouped, virtual, downloadable — with correct AggregateOffer lowPrice / highPrice math and per-variant child offers carrying their own SKU, price, and availability. Identity nodes pull from existing Magento config (logo, legal name, postal address, contact phone, the seven social-profile fields), so going from install to a clean Rich Results Test pass typically takes under 15 minutes on a stock catalog.
Specialist nodes — FAQPage, WebSite SearchAction for Sitelinks Search Box, optional ProductGroup with hasVariant and variesBy — ship behind admin toggles, so each rich-result type is opt-in instead of forced on.
Schema.org JSON-LD emission for every Magento 2 page type, deduplicated by @id and validated against Google Rich Results.
application/ld+json block per page — native Magento markup auto-strippedStores → Configuration → Panth Extensions → Structured Data carries the full configuration surface — Social Profiles (Organization sameAs), Organization Details, Structured Data toggles (JSON-LD), and Breadcrumbs. Every field is [store view]-scoped so a multi-store install can ship a different schema per storefront.

<script type="application/ld+json" data-panth-seo="jsonld"> per page. Everything the module knows about the current entity lives inside one @graph.@id and deep-merges so the Product node emitted by ProductProvider and the AggregateOffer from ConfigurableOfferProvider become one coherent Product with one Offer, no collisions.application/ld+json from product.info.main, breadcrumbs, product.price.final plus all itemprop / itemscope / itemtype microdata attributes on those blocks — so you never ship two Product schemas.data-panth-seo are preserved.Product + scalar Offer (price / availability / itemCondition / seller / priceValidUntil / shippingDetails).Product + AggregateOffer (lowPrice / highPrice / offerCount) with one child Offer per visible variant, each carrying its own price / sku / availability / url.Product + AggregateOffer with lowPrice / highPrice pulled from the bundle option price range. Fixed-price bundles get a single Offer with the fixed final price.Product + AggregateOffer with one child Offer per grouped SKU (name / price / availability / sku / url).Product + Offer.ProductGroup + variesBy on color / size / material.sameAs array merging the 7 dedicated social-profile fields (Facebook, Twitter/X, Instagram, LinkedIn, YouTube, Pinterest, TikTok) with a free-form textarea for anything else.SearchAction for Google Sitelinks Search Box eligibility.#organization by default (since business_type = Organization); switches the @type to LocalBusiness / Store / OnlineStore when configured.position for rich carousel results.datePublished, scaled 1-5 rating from Magento's 0-100 percent votes.Question / acceptedAnswer pairs from product / category / CMS descriptions. Recognises <h2>…?</h2><p>…</p> and <h3>…?</h3><p>…</p> patterns.blog/*, news/*, articles/* or carrying article in their meta keywords.external-video (YouTube / Vimeo).applicableCountry (ISO 3166-1), merchantReturnDays (int), returnMethod, returnFees (properly mapped to the schema.org ReturnFeesEnumeration URL).priceValidUntil from product special_to_date → admin default → auto +1 year fallback; availability from isSalable() / StockRegistry with LimitedAvailability / BackOrder / PreOrder / Discontinued beyond InStock / OutOfStock.priceSpecification with validFrom / validThrough on products with active special prices.acceptedPaymentMethod in Offer from admin list.shippingDetails per region. Only when admin hasn't configured manual delivery_methods.manufacturer) + admin-configurable default fallback brand.hasCertification from a textarea attribute in Authority | Name | ID format.hasEnergyConsumptionDetails with grades A–G.positiveNotes / negativeNotes ItemLists from textarea attributes.material, audience, custom-vocabulary facets).head.additional via view/frontend/layout/default.xml. No JS, no RequireJS, no x-magento-init. Identical output on Hyva, Luma, Breeze, and every custom theme.cacheable="true", so FPC bakes the JSON-LD alongside the rest of the <head> and the providers only run on uncached renders.dateModified / datePublished, review datePublished, article dates, sale validFrom/validThrough all go through DateTimeImmutable::format(ATOM) for strict schema.org compliance.getAttributeText() return values (string | array | false | null from multi-select attributes) are normalised before use, preventing a single multi-select attribute from silently killing the entire Product node.</ inside the JSON is escaped to <\/ so browsers can't be tricked into closing the <script> tag early.The module is a pipeline: admin config → providers → aggregator → head block → <script> tag.
┌─────────────────────────────────────────────────────────────────────┐
│ Admin → Stores → Configuration → Panth Extensions → Structured Data│
│ (toggles, attribute codes, social URLs, organization fields) │
└────────────────────────────────────┬────────────────────────────────┘
│
Helper\Config reads every field
▼
┌─────────────────────────────────────────────────────────────────────┐
│ Aggregator (di.xml-registered pipeline of 24 providers) │
│ │
│ OrganizationProvider → Organization #organization │
│ WebsiteProvider → WebSite #website (+ SearchAction) │
│ BreadcrumbProvider → BreadcrumbList │
│ ProductProvider → Product #product + scalar Offer extras │
│ FaqExtractor → FAQPage (if Q&A pairs detected) │
│ ReviewProvider → Review[] (top 5 approved) │
│ VideoProvider → VideoObject[] (external-video entries) │
│ CmsArticleProvider → Article (blog-like CMS pages) │
│ ReturnPolicyProvider → MerchantReturnPolicy │
│ BrandProvider → Brand │
│ ProductListProvider → ItemList (category pages) │
│ PaymentMethodProvider → Offer (acceptedPaymentMethod) │
│ DeliveryMethodProvider→ Offer (shippingDetails) │
│ MultiRegionShipping → Offer (multi-region shippingDetails) │
│ ConfigurableOffer → Product #product → AggregateOffer │
│ GroupedOffer → Product #product → AggregateOffer (group) │
│ BundleOffer → Product #product → AggregateOffer (bundle) │
│ CustomProperties → Product #product + merchant-defined JSON │
│ EnergyLabel → Product #product + hasEnergyConsumption │
│ Certification → Product #product + hasCertification │
│ SaleEvent → Product #product + priceSpecification │
│ ProductGroup → ProductGroup + hasVariant │
│ ProsCons → Product #product + positiveNotes/negative │
│ SellerProvider → Organization #organization (merged) │
└────────────────────────────────────┬────────────────────────────────┘
│
Deep-merge by @id, deduplicate, validate
▼
┌─────────────────────────────────────────────────────────────────────┐
│ {"@context":"https://schema.org","@graph":[ ...merged nodes... ]} │
│ JSON_UNESCAPED_SLASHES + JSON_UNESCAPED_UNICODE + `</` → `<\/` │
└────────────────────────────────────┬────────────────────────────────┘
│
Block\Head\StructuredData echoes inside:
▼
┌─────────────────────────────────────────────────────────────────────┐
│ <script type="application/ld+json" data-panth-seo="jsonld"> │
│ { ...full @graph... } │
│ </script> │
│ │
│ RemoveNativeMarkupPlugin also runs on AbstractBlock::afterToHtml │
│ and strips Magento's native ld+json + microdata from three blocks: │
│ product.info.main, breadcrumbs, product.price.final │
│ (own blocks carrying data-panth-seo are explicitly preserved) │
└─────────────────────────────────────────────────────────────────────┘
isApplicable(). Product-scoped providers return false on CMS pages, ReturnPolicyProvider returns false when return_policy_days = 0, FaqExtractor defers to Panth_Faq when that module owns the current page. Providers that return false are skipped entirely.Config::isStructuredDataEnabled($provider->getCode()) reads panth_structured_data/structured_data/{code}. Admin disables any provider with a single Yes/No toggle.getJsonLd() returns are collected. Single-node returns and list-of-nodes returns both work.@id get merged key-by-key. Scalars from later providers win, arrays recurse, new keys get appended. Two providers contributing to the same Product, Organization, or Offer always produce one coherent node.@graph → inline the node under @context. Multiple nodes → wrap in @graph.</ in the serialised JSON is replaced with <\/ so the <script> tag can never be closed early by a malicious product name.Before merge (two separate provider outputs on a configurable PDP):
// ProductProvider
{
"@type": "Product",
"@id": "https://example.com/hoodie.html#product",
"name": "Chaz Kangeroo Hoodie",
"offers": {
"@type": "Offer",
"itemCondition": "https://schema.org/NewCondition",
"seller": {"@id": "https://example.com/#organization"},
"priceValidUntil": "2027-04-23",
"shippingDetails": { ... }
}
}
// ConfigurableOfferProvider
{
"@type": "Product",
"@id": "https://example.com/hoodie.html#product",
"offers": {
"@type": "AggregateOffer",
"lowPrice": "52.00",
"highPrice": "52.00",
"offerCount": 15,
"priceCurrency": "USD",
"offers": [ {...15 child offers...} ]
}
}
After merge in @graph:
{
"@type": "Product",
"@id": "https://example.com/hoodie.html#product",
"name": "Chaz Kangeroo Hoodie",
"offers": {
"@type": "AggregateOffer",
"lowPrice": "52.00",
"highPrice": "52.00",
"offerCount": 15,
"priceCurrency": "USD",
"itemCondition": "https://schema.org/NewCondition",
"seller": {"@id": "https://example.com/#organization"},
"priceValidUntil": "2027-04-23",
"shippingDetails": { ... },
"offers": [ {...15 child offers...} ]
}
}
Scalar @type is overridden by the later ConfigurableOfferProvider ("AggregateOffer" wins). Every scalar key unique to one side is preserved. shippingDetails (nested array) comes through untouched from ProductProvider. offers (the child-offer array from ConfigurableOfferProvider) is added without conflict.
| Node | @id pattern | When |
|---|---|---|
Organization |
{store_url}#organization |
Every page |
WebSite |
{store_url}#website |
Every page (toggle) |
BreadcrumbList |
{store_url}#breadcrumb-{sha1 of path} |
Product, category, CMS (2+ items) |
Product |
{product_url}#product |
Product pages |
Offer / AggregateOffer |
inline under Product.offers |
Product pages (shape depends on type) |
Review |
no @id (list items) | Product pages with approved reviews |
AggregateRating |
inline under Product.aggregateRating |
Product pages with votes |
MerchantReturnPolicy |
no @id | Product pages when return_policy_days > 0 |
FAQPage |
{store_url}#faq-{sha1 of path} |
Any page with ≥2 Q&A pairs in description |
Article |
{page_url}#article |
Blog-like CMS pages |
VideoObject |
no @id (list items) | Product pages with external-video gallery entries |
ItemList |
{category_url}#productlist |
Category pages with products |
Brand |
inline under Product.brand |
Product pages when brand attribute has value |
ProductGroup |
{parent_url}#productgroup |
Configurable products (opt-in) |
All URL-valued attributes (og:url, url, image, logo, sameAs) preserve : and / (no entity-encoding). All dates are ISO 8601 (YYYY-MM-DDTHH:MM:SS+00:00). All enumerations (availability, itemCondition, returnFees, returnPolicyCategory, returnMethod) are full schema.org URLs.
Tested end-to-end across Magento's sample catalogue on both Hyva and Luma:
| Type | Sample SKU | Sample URL | Product.offers.@type |
Notes |
|---|---|---|---|---|
| Simple | 24-WB02 |
/compete-track-tote.html |
Offer |
price, availability, itemCondition, seller, priceValidUntil, shippingDetails |
| Configurable | MH01 |
/chaz-kangeroo-hoodie.html |
AggregateOffer |
15 child Offers (one per variant), lowPrice / highPrice / offerCount |
| Bundle (dynamic) | 24-WG080 |
/sprite-yoga-companion-kit.html |
AggregateOffer |
lowPrice / highPrice from bundle option range, offerCount = 1 |
| Bundle (fixed) | any fixed-price bundle | — | Offer |
single Offer with fixed final price |
| Grouped | 24-WG085_Group |
/set-of-sprite-yoga-straps.html |
AggregateOffer |
3 child Offers (one per grouped SKU) |
| Virtual | any virtual product | — | Offer |
same as simple |
| Downloadable | any downloadable product | — | Offer |
same as simple |
Across all variant types the shared offer-level extras (itemCondition, seller @id, priceValidUntil, shippingDetails) merge into the AggregateOffer via the deep-merge path, so a bundle / grouped / configurable product still carries its return policy and product condition on the Offer node.
Every URL below returns HTML with exactly one <script type="application/ld+json" data-panth-seo="jsonld">…</script> block. Substitute your own host for the domain.
Open any URL → right-click → View Page Source → Ctrl-F for application/ld+json. The block carries the full @graph.
Product types — Hyva + Luma test stores
| Type | Hyva | Luma |
|---|---|---|
| Simple | https://your-store.test/compete-track-tote.html |
https://luma.your-store.test/compete-track-tote.html |
| Configurable | https://your-store.test/chaz-kangeroo-hoodie.html |
https://luma.your-store.test/chaz-kangeroo-hoodie.html |
| Bundle | https://your-store.test/sprite-yoga-companion-kit.html |
https://luma.your-store.test/sprite-yoga-companion-kit.html |
| Grouped | https://your-store.test/set-of-sprite-yoga-straps.html |
https://luma.your-store.test/set-of-sprite-yoga-straps.html |
Other page types
| Page | Hyva | Luma |
|---|---|---|
| Home | https://your-store.test/ |
https://luma.your-store.test/ |
| Category (ItemList) | https://your-store.test/gear/bags.html |
https://luma.your-store.test/gear/bags.html |
| CMS | https://your-store.test/about-us |
https://luma.your-store.test/about-us |
| 404 | https://your-store.test/no-route |
https://luma.your-store.test/no-route |
| Search | https://your-store.test/catalogsearch/result/?q=bag |
https://luma.your-store.test/catalogsearch/result/?q=bag |
curl -ks https://your-store.test/chaz-kangeroo-hoodie.html \
| perl -ne 'BEGIN{$/=undef} while (/<script type="application\/ld\+json"[^>]*>(.*?)<\/script>/gs) { print "$1\n" }' \
| python3 -m json.tool
for url in / /compete-track-tote.html /chaz-kangeroo-hoodie.html \
/sprite-yoga-companion-kit.html /set-of-sprite-yoga-straps.html \
/gear/bags.html /about-us /no-route; do
types=$(curl -ks "https://your-store.test$url" \
| perl -ne 'BEGIN{$/=undef} while (/<script type="application\/ld\+json"[^>]*>(.*?)<\/script>/gs) { print "$1\n" }' \
| python3 -c 'import json,sys; d=json.loads(sys.stdin.read()); print(",".join(n.get("@type","?") for n in d.get("@graph",[d])))')
printf " %-40s %s\n" "$url" "$types"
done
Expected output on a stock sample-data install:
/ Organization,WebSite
/compete-track-tote.html Organization,WebSite,BreadcrumbList,Product,Review,MerchantReturnPolicy
/chaz-kangeroo-hoodie.html Organization,WebSite,BreadcrumbList,Product,MerchantReturnPolicy
/sprite-yoga-companion-kit.html Organization,WebSite,BreadcrumbList,Product,MerchantReturnPolicy
/set-of-sprite-yoga-straps.html Organization,WebSite,BreadcrumbList,Product,MerchantReturnPolicy
/gear/bags.html Organization,WebSite,BreadcrumbList,ItemList
/about-us Organization,WebSite
/no-route Organization
Paste in Chrome / Firefox / Safari DevTools console on any page:
JSON.parse(document.querySelector('script[data-panth-seo="jsonld"]').textContent)
['@graph'].forEach(n => console.log(n['@type'], '→', n.name || n.headline || n['@id']))
Google's Rich Results Test is the authoritative validator — it's what determines whether your pages get rich cards in SERPs. Here's the full flow.
Google's crawler can't reach localhost, *.test, VPN-internal, or intranet URLs. If you're testing locally, expose the site through a tunnel first:
# Option A: Cloudflare Tunnel (free, no signup for temporary tunnels)
cloudflared tunnel --url https://your-store.test
# Option B: ngrok (free tier fine for testing)
ngrok http https://your-store.test
Either gives you a public https://xxxx.trycloudflare.com/ or https://xxxx.ngrok.io/ URL. Use that in every validator below.
https://xxxx.trycloudflare.com/compete-track-tote.html).What to expect on a working product page:
To track rich-result performance over time, add the whole site:
If you have Panth_HtmlSitemap or any other sitemap generator, submit it:
https://your-store.com/sitemap.xml).After Google re-crawls (typically 1–7 days):
Product schema)BreadcrumbList schema)Review + AggregateRating)Product + Offer with price + availability + merchant return policy)WebSite + SearchAction)FAQPage schema)Organization + logo)For each invalid URL:
bin/magento cache:flush config full_page).For a single URL:
After Google re-indexes, search for your product in site:your-store.com mode:
site:your-store.com compete track tote
Rich snippets (price, stars, availability badges, breadcrumb path above the title) should appear. Full rollout across all indexed pages takes 2-8 weeks.
Complement Google's Rich Results Test with these before shipping:
| Tool | What it checks | URL |
|---|---|---|
| Schema.org Validator | Stricter than Google — every schema.org issue, nested or not | validator.schema.org |
| Facebook Sharing Debugger | Open Graph + schema.org for Facebook preview cards | developers.facebook.com/tools/debug |
| LinkedIn Post Inspector | Schema preview for LinkedIn shares | linkedin.com/post-inspector |
| Bing Webmaster Tools — Markup Validator | Bing's equivalent of Rich Results Test | bing.com/webmasters/tools/markup-validator |
| opengraph.xyz | Multi-platform preview (Facebook, Twitter/X, LinkedIn, WhatsApp, Slack, iMessage, Discord) | opengraph.xyz |
| Merkle Schema Markup Validator | Batch-check multiple URLs | technicalseo.com/tools/schema-markup-generator |
| Yandex Structured Data Validator | Yandex-specific schema rules | webmaster.yandex.com/tools/microtest |
ProductProvidercurrent_product in registry).Product with @id = {product_url}#product, full image list (from Magento Image helper + media gallery), description (meta_description → short_description → description, ≤5000 chars), brand (via Brand), audience (via PeopleAudience.audienceType from gender attribute), scalar Offer for simple products or offer-level extras (itemCondition, seller, priceValidUntil, shippingDetails, hasMerchantReturnPolicy) for variant types, AggregateRating from Magento's review summary, ISO 8601 dateModified / datePublished.configurable / bundle / grouped) skip scalar price / priceCurrency / availability / url — the specialised provider contributes those via deep-merge.ConfigurableOfferProvider, BundleOfferProvider, GroupedOfferProviderProduct #product contribution containing an AggregateOffer with lowPrice / highPrice / offerCount and for Configurable / Grouped a child offers array of per-variant Offer nodes.Offer.#product @id so the result is one coherent Product node.OrganizationProvider + SellerProviderOrganization #organization with full identity details.SellerProvider uses the same @id = #organization by default; when business_type = LocalBusiness / Store / OnlineStore the merged @type is promoted to the more specific class.sameAs combines 7 dedicated social-profile fields with a free-form textarea.BreadcrumbProviderenable_breadcrumb_priority = Yes, else tiebreaker on depth (longest / shortest).ProductListProviderItemList with one ListItem per visible product in the current category view, preserving pagination order.ReviewProvidercatalog/review/active).Review nodes with ISO 8601 datePublished, scaled 1-5 rating from Magento's 0-100 percent vote aggregate.Panth_Testimonials on its own routes.WebsiteProviderWebSite #website with SearchAction pointing at {store_url}catalogsearch/result/?q={search_term_string} — enables Google Sitelinks Search Box.FaqExtractor<h2>…?</h2><p>…</p> and <h3>…?</h3><p>…</p>.Panth_Faq on its routes.CmsArticleProviderblog/, news/, articles/ or whose meta keywords contain article.Article with headline / description / mainEntityOfPage / url / author / publisher refs + ISO 8601 datePublished / dateModified + up to 5000 chars of articleBody.ReturnPolicyProviderreturn_policy_days > 0.MerchantReturnPolicy with applicableCountry from store config, merchantReturnDays (int), returnMethod, returnFees (mapped to ReturnFeesEnumeration URL).VideoProviderexternal-video.VideoObject for each video (YouTube / Vimeo).CertificationProvider — hasCertification from a textarea attribute.EnergyLabelProvider — hasEnergyConsumptionDetails with grade A–G.ProsConsProvider — positiveNotes / negativeNotes ItemLists.SaleEventProvider — priceSpecification (UnitPriceSpecification) with validFrom/validThrough when special price is active.DeliveryMethodProvider / PaymentMethodProvider / MultiRegionShippingProvider — Offer extensions for shipping + payment.ProductGroupProvider — ProductGroup + hasVariant + variesBy (opt-in; richer variant modelling).CustomPropertiesProvider — admin-editable JSON object deep-merged into the Product node for anything outside the built-in schemas.BrandProvider — standalone Brand node alongside the inline Product.brand.| Issue | Cause | Resolution |
|---|---|---|
| No JSON-LD on any page | FPC cache from before install | bin/magento cache:flush config full_page |
| JSON-LD missing on specific page type | Provider's isApplicable() returned false |
Check the provider's rules — e.g. ItemList needs a category with products; FAQPage needs ≥2 Q&A pairs |
| Duplicate Product schema in SERP | remove_native_markup = No |
Flip to Yes + flush cache |
Breadcrumbs only has 2 items (Home → Product) on PDPs with categories |
Old versions (pre-1.0.1) had a missing Config constant | Upgrade to ≥ 1.0.1 |
Multi-select gender or brand attr silently drops entire Product schema |
Pre-1.0.2 (string) cast on array return |
Upgrade to ≥ 1.0.2 |
Configurable / bundle / grouped offers has leftover price key |
Pre-1.0.2 ProductProvider didn't gate scalar pricing for variant types | Upgrade to ≥ 1.0.2 |
| Google Rich Results Test: "Either 'offers', 'review', or 'aggregateRating' should be specified" | Product has $0.00 final price or is out-of-stock with stock-based logic |
Set a price; check availability logic |
| Google: "Missing field 'priceValidUntil'" | No special_to_date and admin default empty |
Set Default Price Valid Until to a future YYYY-MM-DD (module auto-falls-back to +1 year when both are empty) |
| Google: "Invalid URL in field 'image'" | Product has no image and store logo is a CDN URL that 404s | Upload a valid product image or store logo |
| Google: "Missing field 'returnFees'" | Pre-1.0.2 emitted free-text returnFees that didn't match ReturnFeesEnumeration |
Upgrade to ≥ 1.0.2 |
| FAQPage not detected | Descriptions use a non-standard Q&A HTML shape | Use <h2>Question?</h2><p>Answer.</p> — needs ≥2 pairs |
SearchAction points at wrong URL |
Store base URL misconfigured | Fix Stores → Configuration → Web → Base URLs |
| Organization name renders as "Default Store View" | Store Information → Store Name not set | Set it under Stores → Configuration → General → Store Information |
| Requirement | Versions Supported |
|---|---|
| Magento Open Source | 2.4.4, 2.4.5, 2.4.6, 2.4.7, 2.4.8 |
| Adobe Commerce | 2.4.4, 2.4.5, 2.4.6, 2.4.7, 2.4.8 |
| Adobe Commerce Cloud | 2.4.4 — 2.4.8 |
| PHP | 8.1.x, 8.2.x, 8.3.x, 8.4.x |
| MySQL | 8.0+ |
| MariaDB | 10.4+ |
| Hyva Theme | 1.0+ (native support) |
| Luma Theme | Native support |
| Required Dependency | mage2kishan/module-core ^1.0 |
Works standalone (no other Panth module needed) and composes cleanly with Panth_AdvancedSEO, Panth_Faq, Panth_Testimonials when any of those are installed (specialist providers defer to those modules on their own routes).
composer require mage2kishan/module-structured-data
bin/magento module:enable Panth_Core Panth_StructuredData
bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f
bin/magento cache:flush
app/code/Panth/StructuredData/ in your Magento installation.Panth_Core is installed (required dependency).bin/magento module:enable.bin/magento module:status Panth_StructuredData
# Expected output: Module is enabled
# Every page now carries one JSON-LD block:
curl -ks https://your-store.test/ | grep -c 'application/ld+json'
# Expected output: 1
Navigate to Admin → Stores → Configuration → Panth Extensions → Structured Data. Four fieldsets:
sameAs)| Field | Purpose |
|---|---|
| Facebook URL | Full https://www.facebook.com/yourpage URL added to sameAs |
| Twitter (X) URL | Full https://twitter.com/yourhandle URL |
| Instagram URL | Full Instagram profile URL |
| LinkedIn URL | Full LinkedIn company-page URL |
| YouTube URL | Full YouTube channel URL |
| Pinterest URL | Full Pinterest profile URL |
| TikTok URL | Full TikTok profile URL |
All URLs validated against validate-url and the module's internal HTTPS-only allowlist before being emitted.
| Field | Purpose |
|---|---|
| Legal Name | legalName on the Organization node |
| Logo URL | logo (ImageObject) |
| Phone | contactPoint.telephone — overrides general/store_information/phone |
contactPoint.email |
|
| Street / City / Region / Postal Code | Full PostalAddress |
| Country (ISO 3166-1 alpha-2) | addressCountry — e.g. US, GB, DE |
Additional sameAs URLs |
One URL per line — appended to the social-profile sameAs array |
Leave empty to fall back to core Magento Store Information.
Master toggles + attribute codes + defaults. Every toggle is Yes/No, every attribute code is a plain Magento attribute identifier.
| Setting | Default | What it controls |
|---|---|---|
| Enable Product | Yes | Emits Product + Offer on product pages |
| Enable Breadcrumb | Yes | Emits BreadcrumbList everywhere |
| Enable Organization | Yes | Emits Organization on every page |
| Enable WebSite / SiteLinks | Yes | Emits WebSite + SearchAction |
| Enable Auto-Extracted FAQ Schema | Yes | Scans descriptions for Q&A pairs |
| Enable Article | Yes | Emits Article on blog-like CMS pages |
| Enable Review | Yes | Emits Review + AggregateRating |
| Enable VideoObject | Yes | Emits VideoObject for external-video gallery entries |
| Enable Brand | Yes | Emits Brand node |
| Enable Seller | Yes | Emits Seller (merged into #organization by default) |
| Configurable: Multi-Offer | Yes | Emits one child Offer per configurable variant |
| Remove Native Magento JSON-LD Markup | Yes | Strips Magento's native ld+json on product / breadcrumb blocks |
| Return Policy Days | 30 | merchantReturnDays — set to 0 to disable the MerchantReturnPolicy node |
| Brand Attribute Code | manufacturer |
Product attribute used for Brand name |
| GTIN Attribute Code | (empty) | Product attribute for gtin / gtin13 / ean |
| MPN Attribute Code | (empty) | Product attribute for Manufacturer Part Number |
| Enable Product List Schema | Yes | ItemList on category pages |
| Accepted Payment Methods | (empty) | One method per line (Visa, Mastercard, PayPal) |
| Delivery Methods | (empty) | One method per line (Standard Shipping, Express) |
| Product Condition | New | itemCondition enum (New / Used / Refurbished / Damaged) |
| Default Price Valid Until | (empty) | YYYY-MM-DD — falls back to +1 year |
| Custom JSON-LD Properties (Product) | (empty) | JSON object merged into the Product node |
| Enable ProductGroup + hasVariant | No | Emits ProductGroup with variesBy on color / size / material |
| Enable Pros/Cons | No | Emits positiveNotes / negativeNotes ItemLists |
| Pros Attribute Code | product_pros |
Textarea attribute for pros |
| Cons Attribute Code | product_cons |
Textarea attribute for cons |
| Enable Energy Efficiency Label (EU) | No | Emits hasEnergyConsumptionDetails |
| Energy Class Attribute Code | energy_class |
Attribute for grades A–G |
| Enable Product Certifications | No | Emits hasCertification |
| Certification Attribute Code | certifications |
Textarea, one Authority | Name | ID per line |
| Enable Sale Event / Special Price Details | Yes | Emits priceSpecification with validFrom / validThrough |
| Seller Business Type | Organization | Organization / LocalBusiness / Store / OnlineStore |
| Default Brand Name | (empty) | Fallback when product has no brand attribute |
| Return Policy Type | Refund | refund / exchange (as additionalProperty) |
| Return Fees | free |
free / ReturnFeesCustomerResponsibility / ReturnShippingFees / RestockingFees |
| Limited Stock Threshold | 5 | Below this → LimitedAvailability instead of InStock |
| Setting | Default | What it controls |
|---|---|---|
| Enable Breadcrumb Priority | No | When Yes, product breadcrumbs use category priority weights |
| Breadcrumb Format | Longest (deepest) | shortest / longest — tiebreaker when priorities are equal |
Every field is [store view]-scoped.
After any change, flush caches:
bin/magento cache:flush config full_page
Panth Structured Data is licensed under a proprietary license — see LICENSE.txt. One license per Magento installation.
| Module Category | SEO & Indexing |
|---|---|
| Best For | All Sizes |
No. The head block is cacheable="true" so the full JSON-LD payload is baked into full-page cache. Providers only run on uncached renders; cached hits serve the pre-rendered <script> tag with zero PHP evaluation.
The module only emits data already public on your storefront — product details, store identity, and aggregate review stats. It does not render personally identifiable data in structured-data form.
Yes. Every provider has a Yes/No toggle under Stores → Configuration → Panth Extensions → Structured Data — Product, Breadcrumb, Organization, WebSite, FAQ, Article, Review, VideoObject, Brand, Seller, Product List (ItemList), ProductGroup, Pros/Cons, Energy Label, Certifications, Sale Event, Remove Native Markup.
Yes. No JS, no RequireJS, no x-magento-init. The head block attaches via view/frontend/layout/default.xml to the head.additional container which every Magento theme exposes. Identical output on Hyva, Luma, Breeze, and any custom theme.
Yes. Every admin field is [store view]-scoped, so different store views can ship different Organization data, social profiles, toggles, and attribute codes. URLs in the JSON-LD are scoped to the current store's base URL.
Three ways, in increasing order of effort:
meta_description, short_description, manufacturer, gender — populate those for per-product overrides.StructuredDataProviderInterface, register in di.xml under the providers argument of Panth\StructuredData\Model\StructuredData\Composite (and Aggregator). The aggregator will pick it up automatically.Yes — declare a virtualType in your own module's di.xml with the same class name as the provider you want to replace, or use a preference on Panth\StructuredData\Api\StructuredDataProviderInterface with a type constraint.
Yes, friendly:
Panth_AdvancedSEO: shares the panth_seo/general/enabled master switch when installed; defaults are set locally so standalone installs still work.Panth_Faq: FaqExtractor defers to Panth_Faq on its own routes and when the faq.schema block is mounted and producing data.Panth_Testimonials: ReviewProvider defers on the testimonials route when the module is enabled.Yes. mage2kishan/module-core is a required dependency and is pulled in automatically by Composer.
No — only JSON-LD. Microdata is legacy and mixing the two often produces duplicate-structured-data warnings in Search Console. The RemoveNativeMarkupPlugin actively strips native Magento microdata from the target blocks.
No — any valid WhatsApp number works. However, a WhatsApp Business account is strongly recommended for commercial use (auto-replies, labels, catalog).
Yes. When enabled, Panth Footer takes over the footer.container block and renders its own configurable footer. You can disable it any time to restore the default footer.
No. The notification feed is cached, assets are lightweight, and the JS runs asynchronously after page load.
An emerging community standard proposed at llmstxt.org with a growing list of adopters (Anthropic, Perplexity, Vercel, Stripe, Cloudflare docs all publish one). Not an IETF RFC yet, but stable enough that major AI platforms rely on it.
No. All price-delta logic continues to flow through Magento's standard priceBox and price-option JS components. This module only replaces the visual rendering.
Yes. The module ships with two purpose-built templates — Alpine.js + Tailwind for Hyva and vanilla JS for Luma — and auto-switches based on your active storefront theme.
Unlimited. Each form has a unique identifier and its own submissions scope.
Panth Malware Scanner is built specifically for Magento 2 filesystems — it understands the directory layout, knows which folders are writable from the frontend, and ships signatures tuned for Magento-targeted threats (Magecart skimmers, PolyShell webshells, admin-layout injection). Traditional AV tools scan everything with generic signatures and produce noise.
No. Color, font, and radius changes use CSS custom properties and take effect instantly. Only changes to Tailwind-class-driven tokens (custom spacing scales, new breakpoints) require a rebuild via the Rebuild Theme button.
No. Panth Core is completely free and will remain free forever. It is the foundation library that other (paid) Panth extensions depend on.
Talk to Kishan directly — written quote, scope and timeline within 24 hours. No sales call.
Structured Data JSON-LD for Magento 2