Adding Product JSON-LD on BigCommerce — and keeping it in sync
How to add schema.org Product JSON-LD on BigCommerce, which fields (gtin, brand, offers) actually matter, and how to keep markup synced with the live page.

BigCommerce's Cornerstone theme ships with a basic Product JSON-LD block, but "basic" is doing a lot of work in that sentence — GTIN, brand, and ratings are frequently missing, and multi-variant products confuse the offer shape. This guide covers where the markup lives on a Stencil storefront, which fields Google and AI crawlers actually weight, and how to make sure the JSON-LD never drifts from what a shopper sees on the page.
Where the markup actually lives
On a Stencil-based storefront (Cornerstone and its derivatives), Product JSON-LD is rendered as a Handlebars partial included on the product detail page — typically templates/components/products/schema.html, pulled into templates/pages/product.html. Because it's rendered server-side from the same product context object that populates price, stock, and variant selectors, it updates automatically whenever the underlying catalog data changes — no separate sync job required, as long as you're editing the theme's data bindings and not hardcoding values.
There are two supported ways to touch it:
- Edit the theme file directly. In the control panel, go to Storefront → Themes → Advanced → Edit Theme Files (or edit locally with the Stencil CLI and push). This gives you full access to the Handlebars context —
product.gtin,product.mpn,product.brand,product.reviews, everything. - Inject via Script Manager, under Storefront → Script Manager. Its built-in location options are broad — "Storefront pages" (everything except checkout and order confirmation), Checkout, Order confirmation, or All pages — there's no native "product pages only" scope, so a script placed this way has to check at runtime (e.g., testing the URL pattern or a product-page global like
window.BCData) that it's actually on a PDP before injecting anything. It also only has access to what's already rendered in the DOM, so it's a weaker option if you need identifiers like GTIN that aren't always printed on the visible page. Treat it as a stopgap for stores that can't get theme-file access, not the long-term approach.
Either way, first check whether your theme already emits a Product block via an application/ld+json script tag — search view-source for that MIME type. If one exists, edit it in place. Shipping a second, competing Product block on the same page is a common way to fail rich-result eligibility.
The fields that matter
| Field | Where it comes from | Notes |
|---|---|---|
name | product.title | Required. Must match the visible page heading. |
brand | product.brand.name / product.brand.url | BigCommerce Brands are a separate catalog object linked by brand_id; Google's structured-data guidelines expect brand as a nested Brand (or Organization) node, not a plain string. |
sku | product.sku | Product-level SKU field in the catalog. |
gtin | product.gtin (REST/GraphQL: gtin, alongside mpn and upc) | Optional catalog field, set per product (and per variant for MPN/UPC on variant-level products). Schema.org's gtin property generalizes the older gtin8/gtin12/gtin13/gtin14 properties and auto-detects the right length, so a plain gtin key validates fine — but Google's own Merchant Center identifier guidance still recommends using "the most specific GTIN that applies," which is why Cornerstone's template picks the length-specific key (gtin13, etc.) at render time rather than always emitting gtin. Either form is acceptable; don't rewrite Cornerstone's default just to switch keys. |
offers | product.price (or product.price.price_range for variant products) plus product.condition, product.pre_order, product.out_of_stock | Needs price, priceCurrency, and availability at minimum. |
aggregateRating | product.rating, product.num_reviews | Only render this block when settings.show_product_reviews is true and review count is above zero — an empty aggregateRating is worse than none. |
These map directly to the product identifier fields BigCommerce documents in the catalog (GTIN, UPC, MPN), and to the properties Google's Product snippet documentation treats as required or strongly recommended: name is required, and you need at least one of offers, review, or aggregateRating present for snippet eligibility, with brand, image, and the identifier trio (sku/mpn/gtin) recommended on top of that.
A working example
Here's a trimmed, annotated version of the pattern BigCommerce's own Cornerstone theme uses — Handlebars driving live catalog data into JSON-LD:
<script type="application/ld+json">
{
"@context": "https://schema.org/",
"@type": "Product",
"name": {{{JSONstringify product.title}}},
{{#if product.sku}}"sku": "{{product.sku}}",{{/if}}
{{#if product.mpn}}"mpn": "{{product.mpn}}",{{/if}}
{{#if product.gtin}}"gtin{{length product.gtin}}": "{{product.gtin}}",{{/if}}
"url": "{{product.url}}",
{{#if product.brand}}
"brand": {
"@type": "Brand",
"name": {{{JSONstringify product.brand.name}}}
},
{{/if}}
"description": {{{json (ellipsis (sanitize product.description) 4000)}}},
"image": "{{getImage product.main_image 'zoom_size' (cdn theme_settings.default_image_product)}}",
{{#and settings.show_product_reviews product.reviews.list.length}}
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": "{{product.rating}}",
"reviewCount": "{{product.num_reviews}}"
},
{{/and}}
"offers": {
"@type": "Offer",
"priceCurrency": "{{currency_selector.active_currency_code}}",
"price": "{{#if product.price.with_tax}}{{product.price.with_tax.value}}{{else}}{{product.price.without_tax.value}}{{/if}}",
"availability": "https://schema.org/{{#if product.out_of_stock}}OutOfStock{{else}}InStock{{/if}}",
"url": "{{product.url}}"
}
}
</script>
Rendered for an actual product, that produces:
{
"@context": "https://schema.org/",
"@type": "Product",
"name": "Bosch 18V Cordless Drill Kit",
"sku": "BSH-DRL-18V-KIT",
"mpn": "GSR18V-28FC",
"gtin13": "4059625012345",
"url": "https://tools.example.com/bosch-18v-cordless-drill-kit/",
"brand": { "@type": "Brand", "name": "Bosch" },
"description": "Brushless 18V drill/driver kit with two 4.0Ah batteries...",
"image": "https://cdn.example.com/products/bosch-drill-zoom.jpg",
"aggregateRating": { "@type": "AggregateRating", "ratingValue": "4.6", "reviewCount": "128" },
"offers": {
"@type": "Offer",
"priceCurrency": "USD",
"price": "199.00",
"availability": "https://schema.org/InStock",
"url": "https://tools.example.com/bosch-18v-cordless-drill-kit/"
}
}
Two accuracy issues worth fixing
Variant price ranges. Cornerstone's default template swaps in product.price.price_range for multi-variant products and outputs minPrice/maxPrice inside an @type: "Offer" block — but minPrice/maxPrice aren't valid Offer properties in schema.org; they belong to AggregateOffer (lowPrice, highPrice, offerCount). If your catalog has variant-priced products, switch the @type to AggregateOffer and rename the fields, or better, emit one Offer per SKU so each GTIN/price pair is unambiguous.
Third-party reviews. If you run a review app (Yotpo, Okendo, Judge.me, etc.) instead of native BigCommerce reviews, it usually injects its own aggregateRating/review block. Keep only one source of review structured data per page — two competing aggregateRating nodes is a common cause of Rich Results Test warnings.
How to validate
- View-source vs. rendered DOM:
curl -s https://yourstore.com/product-name/ | grep -A 40 'application/ld+json'shows exactly what a non-JS crawler receives — Stencil renders this server-side, so curl and the browser should match. - Google's Rich Results Test (search.google.com/test/rich-results): paste the live URL, confirm a single
Productentity resolves, and check thatOffer/AggregateOffervalues match what's on the page. - Schema Markup Validator (validator.schema.org) catches property-name errors, like the
minPrice/AggregateOffermismatch above, that Google's tool may not flag. - Spot-check a few SKUs after any catalog import or price sync — GTIN and MPN are easy fields to leave blank at scale.
Verified as of July 2026: field names and the Cornerstone template pattern reflect BigCommerce's current Stencil documentation and public theme source; Script Manager placement options were confirmed against current BigCommerce Developer Center docs. Re-check after major theme or Cornerstone version upgrades.
Getting this markup right assumes the underlying fields — GTIN, MPN, brand, use-case attributes — are actually populated in your catalog, which is where most retailers stall. Anglera enriches that product data continuously in your PIM or BigCommerce catalog itself, so the JSON-LD above always has a real GTIN and brand to render instead of an empty conditional block.
