# Rich Data for Magento 2  -  module reference

> One linked JSON-LD @graph per Magento 2 page: Product, ProductGroup, Organization, breadcrumbs, return and shipping policy, FAQPage. Setup and CLI.

Source: https://angeo.dev/docs/modules/rich-data/
Updated: 2026-10-17

Module reference

# Rich Data for Magento 2  -  module reference

Covers `angeo/module-rich-data` 2.0.0 - Checked 16 October 2026 - MIT licensed

**Rich Data** adds structured data (JSON-LD) to your Magento 2 pages. It writes one linked `@graph` per page, so ChatGPT, Gemini, Perplexity and Google read your catalog as connected data, not as loose pieces.

Every node has a stable `@id`. For example, `offers.seller` points to the same Organization on every page, instead of repeating a name.

Package`angeo/module-rich-data`Latest version2.0.0 (released 2026-09-04)Magento2.4.xPHP8.2 - 8.5LicenseMIT, freeLinks[GitHub](https://github.com/angeo-dev/module-rich-data) - [Packagist](https://packagist.org/packages/angeo/module-rich-data) - [Changelog](https://github.com/angeo-dev/module-rich-data/blob/main/CHANGELOG.md) - [Module page](https://angeo.dev/modules/rich-data/)

**On this page**

1. Schema it adds
2. What it fixes in AEO Audit
3. Requirements
4. Installation
5. Example output
6. Setting it up
7. FAQ markup on CMS pages
8. Checking the output
9. Configuration reference
10. Adding your own schema
11. FAQ
12. Related

## Schema it adds

| Schema | Pages | Main fields |

| `Product` | Product pages | name, description, image, sku, gtin/mpn, brand, offers, aggregateRating |
| `ProductGroup` | Configurable products | hasVariant, variesBy, productGroupID, an offer per variant |
| `Organization` | All pages | name, description, url, logo, sameAs, contactPoint |
| `WebSite` | Homepage | name, url, publisher |
| `BreadcrumbList` | Product, category, CMS pages | full trail |
| `CollectionPage` | Category pages | ItemList of the products on the current page |
| `FAQPage` | CMS pages you choose | question and answer pairs |

Prices follow the store's tax display setting, so the price in the markup matches the price on the page. Configurable, grouped and bundle products publish an `AggregateOffer` with `lowPrice` and `highPrice`.

## What it fixes in AEO Audit

| Signal | Before | After |

| Product schema | Fail / warn | Pass |
| Merchant policies | Fail | Pass, once you turn it on and fill it in |
| Product rating | Warn | Pass, when reviews exist |
| JSON-LD quality  -  breadcrumbs | Warn | Pass |
| FAQPage schema | Warn | Pass, on the pages you choose |

## Requirements

| Magento | 2.4.x (Open Source, Mage-OS or Adobe Commerce)  -  `magento/framework >=103.0.0` |
| PHP | 8.2 - 8.5  -  `~8.2.0||~8.3.0||~8.4.0||~8.5.0` |

## Installation

```
composer require angeo/module-rich-data
bin/magento module:enable Angeo_RichData
bin/magento setup:upgrade
bin/magento setup:di:compile                # production mode only
bin/magento cache:flush
```

Coming from 1.x? Read `UPGRADE.md` in the repository first. The markup shape changes, and prices become tax-inclusive on stores that show prices with VAT.

## Example output

```
{
  "@context": "https://schema.org",
  "@graph": [
    { "@type": "Organization", "@id": "https://shop.example/#organization", "name": "Example Store" },
    {
      "@type": "Product",
      "@id": "https://shop.example/alpine-jacket.html#product",
      "name": "Alpine Jacket",
      "offers": {
        "@type": "Offer",
        "price": "121.00",
        "priceCurrency": "EUR",
        "availability": "https://schema.org/InStock",
        "seller": { "@type": "Organization", "@id": "https://shop.example/#organization" }
      }
    },
    { "@type": "BreadcrumbList", "@id": "https://shop.example/alpine-jacket.html#breadcrumb" }
  ]
}
```

Prefer one script tag per schema, as in 1.x? Set **General -> Output mode** to *One script tag per schema*.

## Setting it up

1. Open **Stores -> Configuration -> Angeo -> Rich Data (JSON-LD)**.
2. In **Organization schema**, add a one- or two-sentence description, a logo URL (512×512 PNG works well) and your social profile URLs. AI engines use this as the summary of your brand.
3. In **Product schema**, turn on **Include Brand** and choose the attribute. If you have barcodes, turn on GTIN / MPN and choose those attributes too.
4. Only when your real return window, shipping cost and delivery times are known, turn on **Merchant policies**. The default country is `US`  -  change it to the countries you ship to.
5. For FAQ pages, list the CMS page identifiers. Empty means any identifier that contains "faq".
6. Check the result with the CLI command below.

Wrong policy values can hurt your eligibility in Google and ChatGPT Shopping. Leave Merchant policies off until the values are true.

## FAQ markup on CMS pages

Data attributes are the reliable way:

```
<div data-faq-question="What is your return policy?"
     data-faq-answer="We offer 30-day returns on all items in original condition.">
</div>
```

Without them, the module looks for an `<h2>` or `<h3>` followed by a `<p>`. The homepage is never treated as an FAQ page.

Google shows FAQ rich results only for government and health sites. Treat FAQPage as content for AI answer engines, not as a Google feature.

## Checking the output

```
bin/magento angeo:rich-data:validate --store=default
bin/magento angeo:rich-data:validate --store=default --product-id=42
bin/magento angeo:rich-data:validate --category-id=11
bin/magento angeo:rich-data:validate --cms-identifier=faq
bin/magento angeo:rich-data:validate --json | jq .
```

| Command | What it does |

| `bin/magento angeo:rich-data:validate` | Validate the JSON-LD output for a product, category or CMS page. |

The exit code is non-zero when a node has no `@id`, an offer is incomplete, or two nodes collide. You can use it in CI.

## Configuration reference

All settings are under **Stores -> Configuration -> Angeo -> Rich Data (JSON-LD)**. The table shows each field, its config path (for `bin/magento config:set`) and its default value.

General (2)

| Setting | Config path | Default |

| Enabled | `angeo_rich_data/general/enabled` | Yes |
| Output mode | `angeo_rich_data/general/output_mode` | `graph` |

Product schema (15)

| Setting | Config path | Default |

| Enabled | `angeo_rich_data/product/enabled` | Yes |
| Include AggregateRating | `angeo_rich_data/product/include_aggregate_rating` | Yes |
| Include SKU | `angeo_rich_data/product/include_sku` | Yes |
| Include Brand | `angeo_rich_data/product/include_brand` | No |
| Brand attribute code | `angeo_rich_data/product/brand_attribute` | `manufacturer` |
| Item condition | `angeo_rich_data/product/condition` | `NewCondition` |
| Include GTIN / MPN identifiers | `angeo_rich_data/product/include_identifiers` | No |
| GTIN attribute code | `angeo_rich_data/product/gtin_attribute` |  -  |
| MPN attribute code | `angeo_rich_data/product/mpn_attribute` |  -  |
| Publish configurable products as ProductGroup | `angeo_rich_data/product/include_variants` | Yes |
| Maximum variants per product | `angeo_rich_data/product/max_variants` | `20` |
| priceValidUntil window (days) | `angeo_rich_data/product/price_valid_days` | `365` |
| In-stock availability URL | `angeo_rich_data/product/availability_in_stock` | `https://schema.org/InStock` |
| Out-of-stock availability URL | `angeo_rich_data/product/availability_out_of_stock` | `https://schema.org/OutOfStock` |
| Include BreadcrumbList (deprecated) | `angeo_rich_data/product/include_breadcrumb` | Yes |

Breadcrumb schema (1)

| Setting | Config path | Default |

| Enabled | `angeo_rich_data/breadcrumb/enabled` | Yes |

Merchant policies (return and shipping) (11)

| Setting | Config path | Default |

| Enable return policy | `angeo_rich_data/merchant_policies/return_enabled` | No |
| Return window (days) | `angeo_rich_data/merchant_policies/return_days` | `30` |
| Applicable country | `angeo_rich_data/merchant_policies/return_country` | `US` |
| Return fee | `angeo_rich_data/merchant_policies/return_fee` | `FreeReturn` |
| Enable shipping details | `angeo_rich_data/merchant_policies/shipping_enabled` | No |
| Flat shipping rate | `angeo_rich_data/merchant_policies/shipping_rate` | `0` |
| Shipping destination country | `angeo_rich_data/merchant_policies/shipping_country` | `US` |
| Handling time, min days | `angeo_rich_data/merchant_policies/handling_days_min` |  -  |
| Handling time, max days | `angeo_rich_data/merchant_policies/handling_days_max` |  -  |
| Transit time, min days | `angeo_rich_data/merchant_policies/transit_days_min` |  -  |
| Transit time, max days | `angeo_rich_data/merchant_policies/transit_days_max` |  -  |

Organization schema (7)

| Setting | Config path | Default |

| Enabled | `angeo_rich_data/organization/enabled` | Yes |
| Organization name | `angeo_rich_data/organization/name` |  -  |
| Organization description | `angeo_rich_data/organization/description` |  -  |
| Logo URL | `angeo_rich_data/organization/logo` |  -  |
| sameAs URLs | `angeo_rich_data/organization/same_as` |  -  |
| Contact telephone | `angeo_rich_data/organization/contact_telephone` |  -  |
| Contact type | `angeo_rich_data/organization/contact_type` | `customer service` |

WebSite schema (2)

| Setting | Config path | Default |

| Enabled | `angeo_rich_data/website/enabled` | Yes |
| Include SearchAction | `angeo_rich_data/website/include_searchaction` | No |

CollectionPage schema (categories) (1)

| Setting | Config path | Default |

| Enabled | `angeo_rich_data/collection_page/enabled` | Yes |

FAQPage schema (2)

| Setting | Config path | Default |

| Enabled | `angeo_rich_data/faq/enabled` | Yes |
| CMS page identifiers | `angeo_rich_data/faq/cms_identifiers` |  -  |

Notes on a few settings:

- **WebSite -> SearchAction** is off by default. Google removed the sitelinks search box in November 2024, so it gives no Google feature. It stays for other consumers.
- **CollectionPage** follows paging and sort order. It does not reflect layered navigation filters, because they are not ready when the page head renders.

## Adding your own schema

Implement `Angeo\RichData\Api\Data\SchemaInterface` and register the builder:

```
<type name="Angeo\RichData\Model\JsonLd\Renderer\SchemaRenderer">
    <arguments>
        <argument name="builders" xsi:type="array">
            <item name="my_schema" xsi:type="object">Vendor\Module\Model\JsonLd\Builder\MySchemaBuilder</item>
        </argument>
    </arguments>
</type>
```

Give your root node an `@id` and it joins the graph like the built-in schemas.

## FAQ

### Will it clash with my theme's JSON-LD?

It can, if your theme or another module also prints Product or Organization schema. Run the validate command and the AEO Audit JSON-LD quality check. Remove the duplicate source you do not need.

### Why are merchant policies off by default?

Because wrong values can hurt your eligibility. Turn them on only when your return window, shipping cost and delivery times are correct.

### Does it support configurable products?

Yes. They are published as `ProductGroup` with one offer per variant, up to the variant limit you set (hard limit 50).

### Does it work with Hyvä?

Yes. The suite modules support Hyvä, and JSON-LD is printed in the page head.

### Why do my prices include tax in the markup?

Prices follow the store's tax display setting, so the markup matches the price a shopper sees. Since 2.0.0 that means tax-inclusive prices on stores that display VAT.

## Related

- [AEO Audit](https://angeo.dev/docs/modules/aeo-audit/)  -  score 20 AI-visibility signals from the command line
- [llms.txt](https://angeo.dev/docs/modules/llms-txt/)  -  generate llms.txt, llms.jsonl, Markdown mirrors and agents.md
- [OpenAI Product Feed](https://angeo.dev/docs/modules/openai-product-feed/)  -  the ChatGPT product feed CSV per store view, for all product types
- [All module reference pages](https://angeo.dev/docs/modules/) - [Compatibility matrix](https://angeo.dev/docs/compatibility/)

This page was written from the `README.md`, `composer.json` and `etc/*.xml` files of [tag 2.0.0](https://github.com/angeo-dev/module-rich-data/tree/2.0.0), read on 16 October 2026. Settings, commands and defaults come straight from that code. If a newer release is out, the README on GitHub wins.
