Skip to content
Log in

Set up a feed

On this page

A feed is assembled from three records. Use this process to publish one at a URL on your store.

Record API name Decides
Query s_c__Query__c Which records the feed contains. One query can be shared by many feeds. See Feed query language
Feed s_c__Feed__c How those records are rendered, and which formats are offered
Store Feed s_c__Store_Feed__c Where the feed is mounted in a particular store, and where it is delivered

A feed is served at /feeds/<path>, publicly and with no authentication. Anyone with the URL can read it, so do not put anything in a feed you would not publish.

Before you start

  • You need permission to create and edit Query, Feed and Store Feed records.
  • These three objects ship without tabs. If you cannot reach their lists, ask your Salesforce administrator to add the tabs to your app navigation.
  • Know the Salesforce API name of the object the feed is built from, such as product2. See How the StoreConnect data model fits together.

Create the query

  1. Open the Query list (s_c__Query__c) and create a record.
  2. Set Object API Name (s_c__Object_API_Name__c) to the base object the feed draws from, such as product2.
  3. (Optional) Fill From, Where, Order and Select to narrow or shape the result. A query with only an object returns every record of that object. See Feed query language for the clauses and the field each one goes in.
  4. Save the record.

Create the feed

  1. Open the Feed list (s_c__Feed__c) and create a record.
  2. Set Query (s_c__Query_Id__c) to the query you just saved.
  3. Set Content Types (s_c__Content_Types__c) to the formats this feed offers, and Default Format (s_c__Default_Format__c) to the one served when a request gives no file extension.
  4. Set Record Mode (s_c__Record_Mode__c) to raw or drop.
  5. Write the Liquid template into Template (s_c__Template__c).
  6. Save the record.

Formats a feed can offer

A feed offers a set of content types, and a request picks one with a file extension: /feeds/products.csv. A request with no extension gets the feed’s default format.

Format Content type
json application/json
jsonl application/x-ndjson
xml application/xml
csv text/csv
html text/html
md text/markdown
txt text/plain

The default format is served whether or not it is also in the offered set, provided it is one of the seven above. An extension the feed does not offer returns 406, not 404.

How each record reaches the template

The feed’s record mode decides what a record is inside the loop.

  • raw, the default, exposes every Salesforce column under its API name, so a product’s slug is {{ record.s_c__slug__c }}.
  • drop wraps each record in the same storefront drop the object gets on a normal page, which reaches computed values that raw columns cannot: a product’s categories, images, store-resolved pricing and variants. An object with no storefront drop, such as a custom object, falls back to raw.

Drop mode does considerably more work per record. Give a drop-mode feed over a large collection a build schedule rather than serving it live.

Write the template

The template owns the output completely. There is no automatic serialization, so the template writes the envelope, the loop and each record.

The requested format is passed in as a format variable, so one template can serve several formats by branching on it:

```liquid

{% if format == ‘json’ %}[{% endif %} {% for record in record_set.records %} … {% endfor %} {% if format == ‘json’ %}]{% endif %} ```

For any output that is not HTML, wrap the body in a {% format format %} block so values are not HTML-escaped.

Mount the feed in a store

  1. Open the Store Feed list (s_c__Store_Feed__c) and create a record.
  2. Set Feed (s_c__Feed_Id__c) to the feed, and Store (s_c__Store_Id__c) to the store it is published on.
  3. Set Path (s_c__Path__c) to the path it is served at. A path of products serves the feed at /feeds/products.
  4. Check Active (s_c__Active__c). A store feed that is not active returns 404 at its path.
  5. Save the record.

Request https://shop.example.com/feeds/products.csv and the feed returns the rendered collection with the matching content type.

Decide between live and pre-built

A feed with no schedule renders on every request. A feed with a schedule is built ahead of time and served from the stored result.

Set a cron expression in Build Schedule on the Feed (s_c__Build_Schedule__c), or on the Store Feed to override it for one store. Setting either makes the feed pre-built. The timezone follows the same order and falls back to the store’s own timezone.

A live feed is paginated, driven by ?page. A pre-built feed is complete and unpaginated.

If a build fails, the previous result keeps being served and the feed is retried when it is next due, so a broken build does not take the feed offline.

Deliver a feed to another system

A Store Feed with a Destination URL (s_c__Destination_URL__c) pushes its default-format result there after each successful scheduled build. The URL must be https. Set Destination Method to put (the default) or post, and Destination Auth Type to blank for none, basic for a username and password, or bearer for a token.

Delivery only happens for pre-built feeds, because a live feed has no complete result to send. If the default format is not one the feed builds, the first format it does build is delivered instead.

:::tip Leave Path blank to make a feed delivery-only. It is then never served at a URL, but still builds on its schedule and pushes to its destination. A store can have any number of these. :::

Keep a large feed buildable

A build renders the whole collection into one result, so by default the entire collection is held in memory at once, and in drop mode a drop per record with it. Past a few thousand records that is what decides whether a feed can be built at all.

Wrap the template in a {% paginate %} block over record_set.paginated_records, which is fetched a page at a time:

```liquid

<?xml version=”1.0” encoding=”UTF-8”?>

{%- paginate record_set.paginated_records by 1000 -%} {%- for product in record_set.paginated_records %} {{ product.sku }} {%- endfor %} {%- endpaginate -%}

```

The rows are identical either way; only peak memory differs. Two things to get right:

  • Keep the header and footer outside the block. Everything inside renders once per page.
  • Do not iterate the collection after {% endpaginate %}. It holds only the last page fetched, so a loop after the block yields that page rather than the whole set.

What a reader sees when something is wrong

  • 404, with the body Feed not found. — no active feed is mounted at that path in this store, or the feed has no query. A Store Feed whose Active checkbox is clear is not found at all.
  • 406, with the body Unsupported format for this feed. — the requested extension is not a format this feed serves. A feed that offers only json returns 406 for .csv.
  • 500, with the body Feed could not be generated. — the feed failed to render. The cause is logged and deliberately kept out of the response, so check the logs rather than the page.

An error is returned in the format that was asked for where StoreConnect recognizes it, so a JSON feed returns {"error": "..."} and a CSV feed returns an error column, rather than a page of HTML.

A live feed runs under a database statement timeout, so a query that is too expensive fails rather than holding the public endpoint open. Builds are bounded separately, by a statement timeout, a row limit, an artifact size limit, and for a paginated build a total duration.

Once the three records are saved and the Store Feed is active, requesting /feeds/<path> returns the rendered collection with the negotiated content type, and /feeds/<path>.<format> returns it in any other format the feed offers.

Was this article helpful?

Was this article helpful?