Keeping structured data in sync from Akeneo to the page
How to map Akeneo product attributes into page-ready JSON-LD and keep them synced end to end, so AI agents and buyers always read the same data your PIM holds.

Enriching a product record in Akeneo is only half the job — the other half is getting that data onto the rendered page, correctly and continuously, as attributes change. This matters more now that AI agents and answer engines parse structured data directly rather than "reading" a page the way a person does. Below is a practical pattern for keeping Akeneo attributes, visible copy, and JSON-LD in lockstep, for manufacturers and distributors running Akeneo as the system of record.
Where sync actually breaks
Most drift between Akeneo and the live page happens in one of three places:
- The pull layer — how you get data out of Akeneo (REST API export, a scheduled job, or event-driven push).
- The mapping layer — how an Akeneo attribute code (
net_weight,manufacturer_part_number) becomes a schema.org property (weight,mpn). - The render layer — whether the visible DOM and the
JSON-LDblock are generated from the same data at the same time, or drift apart because one is server-rendered and the other is patched in later by a different process.
Fixing all three is what keeps "what's in the PIM" and "what an agent reads" the same thing.
Pulling product data out of Akeneo
Akeneo's REST API exposes products with a values object keyed by attribute code, where each value carries its own locale, scope (channel), data, and attribute_type. A localizable, scopable attribute can carry a different value per locale/channel pair; non-localizable or non-scopable attributes carry null in the corresponding field. Any integration that maps Akeneo to JSON-LD needs to resolve locale and scope explicitly — pulling the wrong locale is a common source of a page in English showing German-enriched copy, or vice versa.
{
"identifier": "PUMP-4402",
"values": {
"name": [
{ "locale": "en_US", "scope": null, "data": "4402 Series Centrifugal Pump", "attribute_type": "pim_catalog_text" }
],
"manufacturer_part_number": [
{ "locale": null, "scope": null, "data": "4402-SS-15", "attribute_type": "pim_catalog_identifier" }
],
"net_weight": [
{ "locale": null, "scope": null, "data": { "amount": "18.5", "unit": "KILOGRAM" }, "attribute_type": "pim_catalog_metric" }
]
}
}
For freshness beyond a scheduled export, Akeneo offers two event mechanisms — and the choice matters right now. The legacy Events API (webhooks configured through the PIM's Connect / event subscription settings, exact menu location varies by edition and version) sends a full or partial product payload per product.created / product.updated / product.removed event, batched up to 10 events per request, capped at 4,000 requests/hour with up to 3 event subscriptions. Akeneo's own documentation states this Events API is deprecated and scheduled for retirement on December 31, 2026, in favor of the newer Event Platform, which sends a lightweight payload (just the identifier of the updated entity) and expects the consumer to call the REST API back for the full record. If you're building new integration now, target the Event Platform directly rather than the legacy Events API — and reconfirm the retirement date against Akeneo's current documentation before you rely on it, since published deprecation dates can still move.
Either way, the pattern is the same: event tells you what changed, REST API tells you the current values for that record, at the locale/channel you need.
Mapping Akeneo attributes to schema.org fields
Keep the mapping in one small, versioned config rather than scattered across templates — it's the piece most likely to silently drift. A minimal mapping for a manufacturer PDP:
{
"name": "values.name",
"sku": "identifier",
"mpn": "values.manufacturer_part_number",
"gtin13": "values.gtin",
"weight": "values.net_weight",
"brand": "values.brand"
}
Rendered into JSON-LD, using schema.org Product properties:
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Product",
"name": "4402 Series Centrifugal Pump",
"sku": "PUMP-4402",
"mpn": "4402-SS-15",
"brand": { "@type": "Brand", "name": "Anglera Fluid Systems" },
"weight": { "@type": "QuantitativeValue", "value": "18.5", "unitCode": "KGM" },
"offers": {
"@type": "Offer",
"priceCurrency": "USD",
"price": "1240.00",
"availability": "https://schema.org/InStock",
"url": "https://example.com/products/pump-4402"
}
}
</script>
Google requires name on every Product, plus at least one of offers, review, or aggregateRating; image is recommended rather than strictly required, but omitting it will cost you the visual in a rich result. For the merchant-listing (Shopping) treatment specifically, price and priceCurrency on the Offer are required, and availability is recommended and effectively necessary for eligibility — all three need to reflect real, current values, with price as a plain number or numeric string rather than a formatted currency string. If price or stock lives in an ERP or commerce platform rather than Akeneo, that field in the JSON-LD mapping should point there, not at a stale PIM copy of price.
Rendering visible copy and JSON-LD from one source
The most common cause of an AI agent reading something different from what a buyer sees is architectural: the visible product description is server-rendered from the PIM at build/request time, while the JSON-LD block is injected separately — by a tag manager snippet, a third-party app, or a template that reads from a cache with a different refresh cycle. Once those two paths diverge, they drift independently and nobody notices until an agent surfaces the wrong spec.
The fix is to generate both the visible DOM and the JSON-LD block from the same in-memory product object, in the same server-side render pass, from the same locale/channel-resolved Akeneo values. If your storefront is a headless build (Adobe Commerce, commercetools, BigCommerce, or a custom Next.js/Remix front end pulling from Akeneo via API or connector), that means the JSON-LD template and the visible template both read from the same normalized product model — not two separate calls to Akeneo that could resolve to different completeness or channel scope.
How to validate
- View-source vs. rendered DOM:
curl -s https://example.com/products/pump-4402 | grep -A 30 'application/ld+json'. If the JSON-LD is missing from raw HTML but present in a browser's rendered DOM, it's being injected client-side — most AI crawlers and many search bots won't execute that JavaScript, so they never see it. - Diff against the source of truth: pull the same product from Akeneo's REST API (
GET /api/rest/v1/products/{code}) and diff thename,mpn, price, and availability fields against what's live in the JSON-LD block. Automate this as a scheduled check on a sample of SKUs, not a one-time audit. - Google's Rich Results Test: run the live URL through Rich Results Test to confirm the markup parses and required properties are present.
- Event lag: if using webhooks or the Event Platform, log the time between an Akeneo save and the page reflecting it; a growing gap usually means a queue backing up, not a broken mapping.
Verified as of July 2026 against Akeneo's public API and Events/Event Platform documentation and Google Search Central's structured data guidance; confirm current rate limits, event retirement dates, and menu paths against your Akeneo edition before building, since these details move between releases.
This whole exercise assumes the Akeneo record itself is complete — accurate attributes, specs, and identifiers for every SKU, not just the ones someone had time to enrich by hand. That's the piece Anglera handles: it continuously enriches product data in place in your PIM, so the mapping and rendering work above has a genuinely complete, current record to draw from rather than a partially-filled one.
