All posts
Ray Iyer
Ray Iyer
Co-founder, Anglera

Adding Product JSON-LD on Elastic Path — and keeping it in sync

How to add schema.org Product JSON-LD to an Elastic Path storefront, map PXM fields like gtin and sku correctly, and keep markup synced with the live page.

Adding Product JSON-LD on Elastic Path — and keeping it in sync

Elastic Path (Composable Commerce) is API-first: there's no theme editor or app store to drop structured data into, so Product JSON-LD is something you write once in your storefront code and drive from the same Product Experience Manager (PXM) data that renders the page. Below is the field-by-field mapping, a working example against the shopper API, and a way to make sure the markup can't quietly drift from what buyers actually see.

Why this looks different on Elastic Path

On a themed platform, JSON-LD is usually a template snippet or an app setting. On Elastic Path, your storefront (commonly the Composable Frontend / Next.js starter, or a custom React, Vue, or server-rendered app) calls the PXM Shopper Catalog API directly, so the product detail page (PDP) component already holds every value schema.org wants — name, SKU, price, images. The work is (1) knowing which PXM field maps to which schema.org property, since PXM doesn't use schema.org names natively, and (2) building the JSON-LD object from those same variables rather than a second, hand-maintained copy.

Mapping schema.org Product fields to PXM

Elastic Path's core product resource (returned from GET /catalog/products/:product_id, or the getByContextProduct helper in the @epcc-sdk/sdks-shopper JS SDK) carries name, sku, slug, description, upc_ean, and mpn as native attributes — confirmed in Elastic Path's PXM getting-started guide:

{
  "data": {
    "type": "product",
    "attributes": {
      "name": "BestEver Electric Range",
      "sku": "BE-Electric-Range-1a1a",
      "slug": "bestever-range-1a1a",
      "description": "Induction heating element with a convection oven.",
      "status": "live",
      "commodity_type": "physical",
      "upc_ean": "111122223333",
      "mpn": "BE-R-1111-aaaa-1a1a"
    }
  }
}

Mapping:

  • nameProduct.name
  • skuProduct.sku
  • upc_eanProduct.gtin (use gtin12/gtin13 if you know the code length; plain gtin accepts either per schema.org)
  • mpnProduct.mpn
  • descriptionProduct.description
  • Product images → Product.image, pulled by requesting the main_image relationship (include: ["main_image"]) and reading the returned file's link.href
  • Price → Product.offers, pulled from meta.display_price on the product response

One gap worth flagging: PXM's core attributes don't include a native brand field. Most Elastic Path implementations add it as a custom field via the Flows / custom-data API — create a core Flow with slug: "products", add a brand Field under it, and the value comes back merged into the same product payload once populated. aggregateRating is similar: Elastic Path has no built-in reviews store, so this typically comes from whatever ratings provider you use (Yotpo, Bazaarvoice, PowerReviews, or a manually maintained custom field) rather than PXM itself — don't fabricate a rating if you don't have one.

A working example

Elastic Path prices are integers in the smallest currency unit (e.g., cents for USD), and stock comes from the separate Inventory API's available count rather than the product resource itself. Putting it together for a PDP:

// app/products/[slug]/page.tsx (Next.js App Router, server component)
const response = await getByContextProduct({
  path: { product_id: productId },
  query: { include: ["main_image"] },
});
const product = response.data;

const priceMinorUnits = product.meta.display_price.without_tax.amount;
const currency = product.meta.display_price.without_tax.currency;
const stock = await getStock(product.id); // Inventory API

const jsonLd = {
  "@context": "https://schema.org",
  "@type": "Product",
  name: product.attributes.name,
  sku: product.attributes.sku,
  mpn: product.attributes.mpn,
  gtin: product.attributes.upc_ean,
  description: product.attributes.description,
  image: mainImageUrl,
  brand: {
    "@type": "Brand",
    name: product.attributes.brand ?? "Your Brand Name",
  },
  offers: {
    "@type": "Offer",
    url: `https://www.example.com/products/${product.attributes.slug}`,
    priceCurrency: currency,
    price: (priceMinorUnits / 100).toFixed(2),
    availability:
      stock.available > 0
        ? "https://schema.org/InStock"
        : "https://schema.org/OutOfStock",
  },
};
<script type="application/ld+json">
  {JSON.stringify(jsonLd)}
</script>

The important habit is that jsonLd.name, jsonLd.offers.price, and jsonLd.image are built from the exact same product and stock variables that render the visible price, title, and gallery — not a second query or a cached copy. If the visible page reads from product.meta.display_price, the JSON-LD must too. That's the single biggest source of JSON-LD/page mismatches (and a common cause of Search Console "Price mismatch" warnings): someone updates the display logic later and forgets the structured data lives in a separate block.

Keeping it in sync over time

A few practices that hold up in production:

  • Derive JSON-LD in the same component/function that renders the visible offer, not a separate service or a build step that runs on a stale snapshot.
  • If price or availability is fetched client-side for real-time accuracy, still render the JSON-LD server-side from the same initial API response used for the first paint — search crawlers read the server-rendered HTML, not client-side updates.
  • Add a lightweight test (snapshot or integration) that asserts jsonLd.offers.price === displayedPrice for a sample of PDPs after every deploy touching pricing or PXM field mappings.
  • If a SKU is discontinued or goes out of stock, make sure the same PXM status flag that hides the buy button also flips availability.

How to validate

  • View-source vs. rendered DOM: since the script tag should be server-rendered (Next.js server component or SSR), curl -s https://www.example.com/products/your-slug | grep -A 20 'application/ld+json' should return the full block. If it's missing from curl but visible in browser dev tools, it's being injected client-side only — search engines may not see it.
  • Google's Rich Results Test: paste the live URL into Rich Results Test and confirm the Product type is detected with no missing-field warnings.
  • Schema Markup Validator: run the same URL through Schema.org's validator for a stricter spec-compliance check beyond Google's subset.
  • Spot-check a few PDPs after any pricing, inventory, or PXM attribute-mapping change — this is the step most teams skip until a mismatch warning shows up weeks later.

Verified as of July 2026 against Elastic Path's public developer documentation at developer.elasticpath.com; field names for PXM's core Product resource (name, sku, upc_ean, mpn, slug), the GET /catalog/products/:product_id endpoint, and the Flows-based custom-data mechanism are current as of this writing. The SDK's exact request/response wrapper shape can shift slightly between @epcc-sdk/sdks-shopper releases, so confirm the call signature and response typing against your installed version before shipping, since custom field slugs and price-book configuration are also store-specific.

None of this works if the underlying PXM data is thin — a blank upc_ean, a missing brand field, or a description that's one line of marketing copy leaves you with valid JSON-LD that has nothing meaningful to say. That's the half of the problem Anglera is built for: continuously enriching PIM and PXM records — attributes, identifiers, use-cases, specs — so the mapping in this guide has real, complete data to render on both sides of the page.

Ray Iyer

About the author

Ray IyerCo-founder, Anglera

Ray is a co-founder of Anglera, building the product-data infrastructure for agentic commerce — turning messy catalogs into structured, AI-readable data that buyers and answer engines can find. Previously product at Uber; Stanford CS.

See it on your own SKUs.

A 30-minute walkthrough on your categories and your supplier data.

Book a demo