# Build structured data feeds for AI answer engines

Source: https://support.storeconnect.com/articles/aeo-geo-structured-data-feeds · Last modified 30 July 2026

Use this to build a custom structured data feed, in whatever schema.org format an AI answer engine expects, from any content your store publishes. The pattern below applies equally to a product catalog, a services list, store locations, solution or landing pages, or any other Salesforce-backed content, not only products.

## What StoreConnect generates automatically

Two things are automatic today, and only apply where the underlying content exists:

- **Product structured data** — `Product` and `ProductGroup` JSON-LD (with `Offer`, `Brand`, and variant fields) is rendered automatically on product detail pages. This only applies if your store sells products through the StoreConnect catalog. See [Product rich data snippet](product-rich-data-snippet) to override the built-in output.
- **`BreadcrumbList`** — rendered automatically on product, category, and search pages, to describe site navigation hierarchy.

If your site is service-based, location-based, or otherwise doesn't rely on the product catalog, neither of these applies to you. Everything below is how you build the equivalent yourself, for any content type.

## Before you begin

Decide what content you want to expose and to which schema.org type it maps, for example:

- A product catalog → `Product`, `ProductGroup`, or `ItemList` of products.
- A services list, store locations, or solution pages → `Service`, `LocalBusiness`, or a generic `ItemList` of those records.
- A blog or knowledge base → `Article`, or an `ItemList` of articles.
- Common questions on a page → `FAQPage`.

None of these beyond `Product`/`ProductGroup`/`BreadcrumbList` are generated automatically. You build them the same way regardless of content type: query the backing records, then render valid schema.org output yourself.

## Enumerate products for the current store

A product is included in a store based on its **Price Book Entry**, not a direct or junction lookup on the product record itself. `Product2` has no store-scoping field at all; a store's catalog is the set of products with an active entry in the Pricebook assigned to that store. Use the `current_pricebook` global, which already resolves to the current store's assigned Pricebook:


```liquid

{%- paginate current_pricebook.pricebook_entries by 20 -%}
  {%- for entry in current_pricebook.pricebook_entries -%}
    {%- assign product = entry.product -%}
    {{ product.name }}
  {%- endfor -%}
{%- endpaginate -%}
```


:::warning
`pricebook_entries` is a `PaginatedList`. A plain `{% for %}` loop over it, without wrapping the loop in [`paginate`](paginate-tag-reference), silently returns zero items — it does not lazily fetch results on its own. Wrap the loop in `{% paginate current_pricebook.pricebook_entries by <page_size> %} ... {% endpaginate %}`, and loop the same collection name inside the block (not a `paginate.items` accessor). This is confirmed behavior, not a documentation assumption.
:::

For a custom object backing services, locations, or other non-catalog content, use the [`query` tag](liquid-query) instead, since those objects don't go through a Pricebook:


```liquid

{%- query 'Your_Custom_Object__c' as records, s_c__store__c: current_store.id -%}
{%- for record in records -%}
  {{ record.name }}
{%- endfor -%}
```


The store-scoping field shown here (`s_c__store__c`) is illustrative — confirm the actual field name for your object against a live org's schema before relying on it; it varies by object and some objects scope to a store through a junction rather than a direct lookup.

## Serve the feed

Create a content [page](content-pages) with a file extension in its **Path** field (for example `.json` or `.xml`) so it renders as raw, layout-free output instead of themed HTML. Build the schema.org structure directly in the page's Content Body using Liquid:


```liquid

{
  "@context": "https://schema.org",
  "@type": "ItemList",
  "itemListElement": [
    {%- assign i = 0 -%}
    {%- paginate current_pricebook.pricebook_entries by 20 -%}
    {%- for entry in current_pricebook.pricebook_entries -%}
      {%- assign i = i | plus: 1 -%}
      {%- assign product = entry.product -%}
      {
        "@type": "ListItem",
        "position": {{ i }},
        "name": "{{ product.name }}",
        "url": "{{ product.url }}"
      }{% unless forloop.last %},{% endunless %}
    {%- endfor -%}
    {%- endpaginate -%}
  ]
}
```


:::warning
StoreConnect does not automatically serialize this output. Your template is responsible for producing syntactically valid JSON or XML, including correct comma placement between loop iterations (as in the `forloop.last` check above) and escaping any field value that could contain a quote or control character.
:::

This example builds an `ItemList` of products, but the same page-plus-`query` pattern (see the previous section) builds a feed of services, locations, or any other record type. A non-catalog example (a location or service listing) follows the identical shape: query the object holding that content, loop, and emit the schema.org type that matches it (`LocalBusiness`, `Service`, or a generic `ItemList`) instead of `Product`.

## Validate your output

The tools are the same regardless of content type or schema used:

- [Google's Rich Results Test](https://search.google.com/test/rich-results) checks eligibility for Google's rich results.
- The [Schema.org Validator](https://validator.schema.org/) checks any schema.org JSON-LD against the specification generally.

## Related patterns

An `llms.txt` or companion feed built this way can link to, or be linked from, other feeds you create the same way. See [Answer engine and generative engine optimization](answer-engine-optimization) for the broader AEO/GEO picture, including how to control which AI crawlers can reach these feeds at all.

---

## Follow StoreConnect

- [Email Newsletter](https://getstoreconnect.com/c/lp-newsletter)
- [LinkedIn Newsletter](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7444956928444862464)
- [YouTube](https://www.youtube.com/channel/UCngKdP2x8l1wcbAKW3tvU8g)
- [LinkedIn](https://www.linkedin.com/company/storeconnect)
- [X / Twitter](https://x.com/storeconnecthq)

## Popular Links

- [Partners](https://getstoreconnect.com/partners)
- [News](https://getstoreconnect.com/articles/news)
- [Events](https://getstoreconnect.com/articles/events)
- [Feature Comparison](https://getstoreconnect.com/how-we-compare)
- [Download a free trial](https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3A00000FMkeKUAT)
- [Book a Demo](https://getstoreconnect.com/contact)

## Documentation

- [Help documentation](https://support.storeconnect.com/help-documentation)
- [Videos & tutorials](https://support.storeconnect.com/videos-tutorials)
- [Developer reference](https://support.storeconnect.com/developer-reference)
- [Release notes](https://support.storeconnect.com/release-notes)
- [Troubleshooting](https://support.storeconnect.com/troubleshooting)
- [Trust Center](https://trust.getstoreconnect.com/)
- [Status Page](https://status.storeconnect.com/)

## Contact

- info@getstoreconnect.com
- US +1 415 745 3230
- AUS +61 2 8365 2308

100 S Ashley Dr, Suite 600-2461
Tampa FL 33602-600 USA

Level 22, Sydney Place
180 George Street
Sydney, NSW, 2000, AUS

---

StoreConnect Support — https://support.storeconnect.com/articles/aeo-geo-structured-data-feeds