{"title":"Build structured data feeds for AI answer engines","slug":"aeo-geo-structured-data-feeds","url":"https://support.storeconnect.com/articles/aeo-geo-structured-data-feeds","url_markdown":"https://support.storeconnect.com/articles/aeo-geo-structured-data-feeds.md","subtitle":null,"summary":"Use Liquid queries and content pages to build structured data feeds for AI answer engines and generative search tools, covering products, services, locations, and any other content a store publishes.","type":"Developer_Documentation","video_url":"","keywords":"AEO, GEO, structured data, JSON-LD, schema.org, llms.txt, liquid query, content page, feed, CollectionPage, ItemList, LocalBusiness, Service, FAQPage, answer engine optimization","last_modified":"2026-07-30T00:28:16+0000","body_markdown":"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.\n\n## What StoreConnect generates automatically\n\nTwo things are automatic today, and only apply where the underlying content exists:\n\n- **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.\n- **`BreadcrumbList`** — rendered automatically on product, category, and search pages, to describe site navigation hierarchy.\n\nIf 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.\n\n## Before you begin\n\nDecide what content you want to expose and to which schema.org type it maps, for example:\n\n- A product catalog → `Product`, `ProductGroup`, or `ItemList` of products.\n- A services list, store locations, or solution pages → `Service`, `LocalBusiness`, or a generic `ItemList` of those records.\n- A blog or knowledge base → `Article`, or an `ItemList` of articles.\n- Common questions on a page → `FAQPage`.\n\nNone 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.\n\n## Enumerate products for the current store\n\nA 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:\n\n\n```liquid\n\n{%- paginate current_pricebook.pricebook_entries by 20 -%}\n  {%- for entry in current_pricebook.pricebook_entries -%}\n    {%- assign product = entry.product -%}\n    {{ product.name }}\n  {%- endfor -%}\n{%- endpaginate -%}\n```\n\n\n:::warning\n`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 \u003cpage_size\u003e %} ... {% endpaginate %}`, and loop the same collection name inside the block (not a `paginate.items` accessor). This is confirmed behavior, not a documentation assumption.\n:::\n\nFor 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:\n\n\n```liquid\n\n{%- query 'Your_Custom_Object__c' as records, s_c__store__c: current_store.id -%}\n{%- for record in records -%}\n  {{ record.name }}\n{%- endfor -%}\n```\n\n\nThe 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.\n\n## Serve the feed\n\nCreate 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:\n\n\n```liquid\n\n{\n  \"@context\": \"https://schema.org\",\n  \"@type\": \"ItemList\",\n  \"itemListElement\": [\n    {%- assign i = 0 -%}\n    {%- paginate current_pricebook.pricebook_entries by 20 -%}\n    {%- for entry in current_pricebook.pricebook_entries -%}\n      {%- assign i = i | plus: 1 -%}\n      {%- assign product = entry.product -%}\n      {\n        \"@type\": \"ListItem\",\n        \"position\": {{ i }},\n        \"name\": \"{{ product.name }}\",\n        \"url\": \"{{ product.url }}\"\n      }{% unless forloop.last %},{% endunless %}\n    {%- endfor -%}\n    {%- endpaginate -%}\n  ]\n}\n```\n\n\n:::warning\nStoreConnect 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.\n:::\n\nThis 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`.\n\n## Validate your output\n\nThe tools are the same regardless of content type or schema used:\n\n- [Google's Rich Results Test](https://search.google.com/test/rich-results) checks eligibility for Google's rich results.\n- The [Schema.org Validator](https://validator.schema.org/) checks any schema.org JSON-LD against the specification generally.\n\n## Related patterns\n\nAn `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."}