Skip to content

Feeds

cms.feed.rss() and cms.feed.atom() build a syndication feed from two arguments: the feed’s own details, and a list of items. Everything about which objects go in and how each one looks stays in the template, where |filter, |sortBy and |map already live.

Create a builder page with a route ending in /rss (or .rss), and a template like this:

{{ cms.feed.rss(
{
title: 'Total CMS Releases',
link: cms.builder.canonicalUrl('changelog'),
self: cms.builder.canonicalUrl('changelog-rss'),
description: 'New capabilities, refinements, and fixes.',
},
cms.collection.objects('changelog')|sortBy('date')|reverse|map(e => {
title: e.version ~ '' ~ e.title,
link: cms.collection.canonicalObjectUrl('changelog', e),
id: e.id,
date: e.date,
content: e.changelog|markdown,
})
) }}

That is the whole template — no layout, no XML boilerplate. Escaping, CDATA, RFC-2822 dates and the atom:link self reference are handled for you.

The route’s extension sets the Content-Type, so /changelog/rss and /changelog/feed.rss both serve application/rss+xml. See Site Builder for how that works.

KeyRequiredNotes
titleyes
linkyesThe page a reader should visit — not the feed’s own URL
descriptionyes
selfAtom onlyThe feed’s own URL. Readers use it to re-fetch
languagenoe.g. en-us
updatednoDefaults to the newest item’s date
generatorno
copyrightno

self is required for Atom because readers use it to re-subscribe, and there is no safe value to guess — pointing it at link would hand every subscriber a URL for the HTML page instead of the feed.

KeyNotes
title
linkMade absolute against your domain if relative
idThe item’s identity. Defaults to link
dateISO string, timestamp, or DateTime
contentHTML. Run Markdown fields through |markdown first
summaryShort form. RSS uses it in place of content if given
authorA name, or {name, email, uri}
mediaAn enclosure — see below

Items are emitted in the order you supply them, so sort before you map.

A feed reader treats the id as the item’s identity and re-announces anything whose id changes. Prefer something stable and independent of the URL — an object id is ideal:

id: e.id,

Atom is stricter: an entry id must be an IRI, so a short key like v3-5-0 is not legal there. In an Atom feed the item link is used instead, which is what feed generators generally do.

media attaches an enclosure — a podcast episode, a video, an image. The short form is a URL:

media: e.audio.link,

The type is guessed from the extension and the length reported as zero. For a podcast, give real values instead — image and file fields already carry mime and size:

media: {
url: e.audio.link,
type: e.audio.mime,
length: e.audio.size,
},

Podcast clients use length for progress and buffering, so it is worth supplying. One enclosure per item — RSS 2.0 permits no more.

Same arguments, different renderer:

{{ cms.feed.atom(meta, items) }}

Serve it from a route ending in .xml, and remember meta.self.

Total CMS also serves /feed/rss/{collection} without any template. That endpoint maps fields by name — which field is the title, which is the content — and that is its limit: it cannot build a title out of two fields, and it cannot run content through |markdown, so a Markdown field arrives at the subscriber as raw - **like this**.

Use the endpoint when your collection already has a plain-text summary field and a usable title. Build the feed in Twig when you need control over either.