Skip to content
Log in

Serve custom XML, RSS, JSON, and text feeds from a page

On this page

Use this process to publish a file such as products.json, news.xml, or categories.txt at a path on your store, built from live store data. It uses the same mechanism as llms.txt: a Page record with a file extension in the Path field, whose Content Body is rendered as Liquid and returned without the theme layout.

Choose between a page and the Feeds object

StoreConnect has two ways to publish a file for another system to read.

  Page with a file extension Feeds object
URL Any path on the store, such as /news.xml Always under /feeds/, such as /feeds/products.csv
Formats One per page, set by the extension in Path Several from one template, chosen by the requested extension
Data Store-scoped Liquid globals such as current_store, all_products, and all_articles Any object StoreConnect syncs to the storefront, picked by a Query record
Size A small, fixed number of records per request Paginated live feeds, or pre-built on a schedule for large collections
Delivery Read on request only Read on request, or pushed to another system after each scheduled build
Records to create One Page A Query, a Feed, and a Store Feed

Use a page when a reader expects a file at a specific path, such as a partner who fetches /news.xml, and the content is a short list drawn from the current store. Use the Feeds object when the feed covers a large catalog, draws from a custom object, needs several formats, or must be built on a schedule and delivered. See set up a feed.

How the format is set

The extension at the end of Path sets the response content type. The page renders without the header, footer, or theme wrapper, and current_request.content_type returns the content type for the request. Use one of these extensions.

Path ends in Content type
.txt text/plain
.xml application/xml
.json application/json
.csv text/csv
.md text/markdown

Serve an RSS feed from a .xml path, such as news.xml. RSS readers accept application/xml. Do not use a .rss path, which returns a server error.

Before you start

  • You can create and edit Page records (s_c__Page__c) for the store.
  • You have chosen a Path that no other page on the store uses. A page with the Path sitemap.xml or robots.txt replaces the generated file; see sitemap file and edit robots.txt.
  • The Path does not start with feeds/, which is the path the Feeds object serves feeds from.

Create the page

  1. Go to the Pages list for the store and select New.
  2. Set Path to the file name and extension, such as news.xml (no leading /).
  3. Set Title to a name for the record, such as News feed.
  4. Check Publish the page on the site.
  5. Check Hide Page from Navigation and Menus, so the file does not appear in the navigation, footer, or generated sitemap. The page still appears in on-site search results.
  6. Replace the default {{ content_page | render_content_blocks }} snippet in Content Body (Markdown) (s_c__Body_Markdown__c) with the template for your format from the examples below.
  7. Save the record.

Open https://shop.example.com/news.xml. The browser shows the rendered file, with real values from your store and no theme around it.

Escape values for the format

You write the whole file in the template, so you are responsible for valid syntax, including the commas between JSON items.

Text read from a store record reaches Liquid HTML-escaped, so Salt & Pepper arrives as Salt & Pepper. Handle text values in each format as follows.

  • XML and RSS — print values as they arrive. HTML-escaped text is valid XML text. Do not add | escape, which escapes the value a second time.
  • JSON — convert each text value with | unescape | json, which restores the original characters and writes them as a quoted JSON string. Do not wrap the result in your own quotes.
  • Plain text — add | unescape to text values, so readers see & rather than &.
  • Rich text — fields such as summary_content contain HTML. In XML or RSS, place them inside a <![CDATA[ ... ]]> section.

StoreConnect adds the XML declaration (<?xml version="1.0" encoding="UTF-8"?>) to every .xml page. Start an XML or RSS Content Body with the root element, such as <rss>. A second declaration makes the file invalid XML.

Examples

Each example is a complete Content Body for the Path shown. The examples read a fixed number of records with the paginate filter, because all_products and all_articles are paginated collections and a plain loop over them returns nothing. See paginate.

XML (products.xml)

```liquid

{%- assign products = all_products | paginate: 50 -%} {%- for product in products %} {{ product.name }} {{ product.product_code }} {{ product.url }} {%- endfor %}

```

RSS 2.0 (news.xml)

```liquid

{{ current_store.name }} {{ current_store.url }}</link> {{ current_store.meta_description }} {%- assign articles = all_articles | paginate: 20 -%} {%- for article in articles %} {{ article.title }} {{ article.url }}</link> {{ article.url }} {{ article.publish_on | date: "%a, %d %b %Y %H:%M:%S %z" }} {{ article.summary_content }} {%- endfor %}

```

all_articles returns the newest articles first, by publish date. The date format string writes pubDate in the RFC 822 form that RSS 2.0 requires, such as Thu, 01 Jul 2021 09:30:00 +0000. The date filter translates day and month names into the store’s language, so on a store that is not in English, pubDate is no longer in RFC 822 form and some RSS readers reject it.

JSON (products.json)

```liquid

{ “store”: {{ current_store.name | unescape | json }}, “url”: {{ current_store.url | json }}, “products”: [ {%- assign products = all_products | paginate: 50 -%} {%- for product in products %} { “name”: {{ product.name | unescape | json }}, “code”: {{ product.product_code | unescape | json }}, “url”: {{ product.url | json }} }{% unless forloop.last %},{% endunless %} {%- endfor %} ] } ```

The {% unless forloop.last %} check places a comma after every item except the last, which JSON requires.

Plain text (categories.txt)

```liquid

{{ current_store.name | unescape }} {{ current_store.url }}

Product categories {% for category in current_store.navigation_categories %} - {{ category.name | unescape }}: {{ category.url }} {% endfor %} ```

For a file that describes your store to AI tools, follow generate an llms.txt file for your site, which lists the store fields that are available and the ones that do not exist.

Check the result

Request the URL after saving and check it with a validator for the format, such as an online JSON or XML validator or an RSS feed validator. If the result is wrong, match what you see to one of these causes.

  • A blank value usually means an attribute that does not exist. Check it against the Store, Product, or Article object reference.
  • &amp; or &quot; in the output means a text value was not unescaped. Add | unescape for JSON and plain text. In XML, &amp; is correct and parsers read it as &.
  • Liquid error text in the output means a filter or tag that does not exist, or a syntax mistake in the template.
  • A not-found page means the Page record is missing, has a different Path, or does not have Publish the page on the site checked.
  • A themed web page rather than the raw file means the Path is missing the extension.
  • An XML parse error about the XML declaration means the Content Body starts with its own <?xml declaration. Remove it, because StoreConnect adds one.

Stealth mode

Stealth mode suppresses robots.txt, the sitemap, and the merchant feeds. A custom page at any other path is not on that list, so it keeps serving to anyone who requests the URL. Stealth mode changes two things for these pages.

  • The store’s robots.txt returns Disallow: /, which tells compliant crawlers not to request any path, including your feed.
  • Every response, including your feed, carries an X-Robots-Tag: noindex, nofollow header, which tells compliant crawlers not to index it.

A page with the Path sitemap.xml returns a 404 while stealth mode is on, and a page with the Path robots.txt is ignored. A robots.txt page is also ignored while the store is in test mode. Stealth mode does not restrict access, so do not rely on it to keep a feed private.

Remove a feed

Delete the Page record, or uncheck Publish the page on the site. Requests to the path then return a 404. If another system reads the feed, tell the owner of that system first, because the reader receives a 404 rather than an empty file.

Was this article helpful?

Was this article helpful?