{"title":"Set up a feed","slug":"set-up-a-feed","url":"https://support.storeconnect.com/articles/set-up-a-feed","url_markdown":"https://support.storeconnect.com/articles/set-up-a-feed.md","subtitle":null,"summary":"Create the Query, Feed and Store Feed records that publish a feed at a public URL: choose the formats it offers, write its template, decide whether it renders live or is built to a schedule and delivered, and keep a large feed within its build limits.","type":"Developer_Documentation","video_url":"","keywords":"set up a feed, feed endpoint, feed template, record mode, drop mode, build schedule, cron, destination url, feed delivery, paginate, live feed, prebuilt feed, feed formats, json feed, csv feed, xml feed","last_modified":"2026-10-01T03:35:10+0000","body_markdown":"A feed is assembled from three records. Use this process to publish one at a URL on your store.\n\n| Record | API name | Decides |\n|---|---|---|\n| **Query** | `s_c__Query__c` | Which records the feed contains. One query can be shared by many feeds. See [Feed query language](feed-query-language) |\n| **Feed** | `s_c__Feed__c` | How those records are rendered, and which formats are offered |\n| **Store Feed** | `s_c__Store_Feed__c` | Where the feed is mounted in a particular store, and where it is delivered |\n\nA feed is served at `/feeds/\u003cpath\u003e`, publicly and with no authentication. Anyone with the URL can read it, so do not put anything in a feed you would not publish.\n\n## Before you start\n\n- You need permission to create and edit **Query**, **Feed** and **Store Feed** records.\n- These three objects ship without tabs. If you cannot reach their lists, ask your Salesforce administrator to add the tabs to your app navigation.\n- Know the Salesforce API name of the object the feed is built from, such as `product2`. See [How the StoreConnect data model fits together](data-model-overview).\n\n## Create the query\n\n1.  Open the **Query** list (`s_c__Query__c`) and create a record.\n2.  Set **Object API Name** (`s_c__Object_API_Name__c`) to the base object the feed draws from, such as `product2`.\n3.  (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](feed-query-language) for the clauses and the field each one goes in.\n4.  Save the record.\n\n## Create the feed\n\n1.  Open the **Feed** list (`s_c__Feed__c`) and create a record.\n2.  Set **Query** (`s_c__Query_Id__c`) to the query you just saved.\n3.  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.\n4.  Set **Record Mode** (`s_c__Record_Mode__c`) to `raw` or `drop`.\n5.  Write the Liquid template into **Template** (`s_c__Template__c`).\n6.  Save the record.\n\n### Formats a feed can offer\n\nA 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.\n\n| Format | Content type |\n|---|---|\n| `json` | `application/json` |\n| `jsonl` | `application/x-ndjson` |\n| `xml` | `application/xml` |\n| `csv` | `text/csv` |\n| `html` | `text/html` |\n| `md` | `text/markdown` |\n| `txt` | `text/plain` |\n\nThe 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`.\n\n### How each record reaches the template\n\nThe feed's record mode decides what a record is inside the loop.\n\n- **`raw`**, the default, exposes every Salesforce column under its API name, so a product's slug is `{{ record.s_c__slug__c }}`.\n- **`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`.\n\nDrop mode does considerably more work per record. Give a drop-mode feed over a large collection a build schedule rather than serving it live.\n\n### Write the template\n\nThe template owns the output completely. There is no automatic serialization, so the template writes the envelope, the loop and each record.\n\nThe requested format is passed in as a `format` variable, so one template can serve several formats by branching on it:\n\n\n```liquid\n\n{% if format == 'json' %}[{% endif %}\n{% for record in record_set.records %}\n  ...\n{% endfor %}\n{% if format == 'json' %}]{% endif %}\n```\n\n\nFor any output that is not HTML, wrap the body in a `{% format format %}` block so values are not HTML-escaped.\n\n## Mount the feed in a store\n\n1.  Open the **Store Feed** list (`s_c__Store_Feed__c`) and create a record.\n2.  Set **Feed** (`s_c__Feed_Id__c`) to the feed, and **Store** (`s_c__Store_Id__c`) to the store it is published on.\n3.  Set **Path** (`s_c__Path__c`) to the path it is served at. A path of `products` serves the feed at `/feeds/products`.\n4.  Check **Active** (`s_c__Active__c`). A store feed that is not active returns `404` at its path.\n5.  Save the record.\n\nRequest `https://shop.example.com/feeds/products.csv` and the feed returns the rendered collection with the matching content type.\n\n## Decide between live and pre-built\n\nA feed with no schedule renders on every request. A feed with a schedule is built ahead of time and served from the stored result.\n\nSet 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.\n\nA live feed is paginated, driven by `?page`. A pre-built feed is complete and unpaginated.\n\nIf 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.\n\n## Deliver a feed to another system\n\nA **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.\n\nDelivery 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.\n\n:::tip\nLeave **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.\n:::\n\n## Keep a large feed buildable\n\nA 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.\n\nWrap the template in a `{% paginate %}` block over `record_set.paginated_records`, which is fetched a page at a time:\n\n\n```liquid\n\n\u003c?xml version=\"1.0\" encoding=\"UTF-8\"?\u003e\n\u003cproducts\u003e\n{%- paginate record_set.paginated_records by 1000 -%}\n{%- for product in record_set.paginated_records %}\n  \u003cproduct\u003e\n    \u003csku\u003e{{ product.sku }}\u003c/sku\u003e\n  \u003c/product\u003e\n{%- endfor %}\n{%- endpaginate -%}\n\u003c/products\u003e\n```\n\n\nThe rows are identical either way; only peak memory differs. Two things to get right:\n\n- **Keep the header and footer outside the block.** Everything inside renders once per page.\n- **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.\n\n## What a reader sees when something is wrong\n\n- **`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.\n- **`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`.\n- **`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.\n\nAn 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.\n\nA 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.\n\nOnce the three records are saved and the **Store Feed** is active, requesting `/feeds/\u003cpath\u003e` returns the rendered collection with the negotiated content type, and `/feeds/\u003cpath\u003e.\u003cformat\u003e` returns it in any other format the feed offers."}