Skip to content Skip to footer navigation

Serving Multiple RSS Feeds in Statamic

When it comes to RSS, it’s more tempting to subscribe to a feed when it's exactly what you want. For example, what if you want to subscribe to BBC News, but you don't really care for politics? Luckily bigger websites often provide multiple “sub” feeds so you can subscribe to specific topics rather than everything under the sun.

What if you want to offer multiple feeds on your own site? I was stuck with this question—since I write about a variety of things. I write music reviews, film reviews, posts about Statamic, and thought-pieces. I know not everyone cares for my music taste, so I decided to figure out how to offer different RSS feeds using Statamic since this is what my site is currently built on.

The result is my Subscribe page, where I've output all the possible RSS feeds on this site so fellow RSS-lovers can pick and choose. So how did I do this?

1. Conditionally Output an RSS Title in Your <head>

Adding a link to the head means that visitors can just dump the page URL in an RSS reader and it'll fetch the feed automatically. The minimum amount of code you’d need to link to an RSS feed would be like this:

<link rel="alternate" type="application/rss+xml" title="Jay's Blog feed" href="{{ current_url }}/rss">

This would work well for a blog, for example.

But since we want to offer different feed titles depending on the page, we'll need to use an if statement to understand which page we're on.

We can easily set variables in Statamic at the very top of the antlers file.

At the top of my resources/views/blog/index.antlers.html file I've added:

---
rss: blog
---

Subsequently at the top of my resources/views/listening/index.antlers.html file I have:

---
rss: listening
---

And at the top of my resources/views/watched/index.antlers.html file I have:

---
rss: watched
---

We now want an if statement in our head to detect the page and conditionally serve the relevant feed link. In my case I've broadly split RSS feeds into my Blog, Listening, and Watched. You could probably make this code a little leaner by simply outputting the title dynamically, but I wanted a little more control over the title so this is the way I did it.

{{ if view:rss == "blog" }}
    <link rel="alternate" type="application/rss+xml" title="Jay's Blog" href="{{ current_url }}/rss">
{{ elseif view:rss == "listening" }}
    <link rel="alternate" type="application/rss+xml" title="Jay George &ndash; Intrigue / Listening" href="{{ current_url }}/rss">
{{ elseif view:rss == "watched" }}
    <link rel="alternate" type="application/rss+xml" title="Jay George &ndash; Intrigue / Watched" href="{{ current_url }}/rss">
{{ /if }}

2. Create a Dynamic RSS Template

Create a new file called resources/views/rss.antlers.htmlThis is the file we'll use to generate a dynamic RSS feed depending on the URL. For reference, here is a simple static RSS feed:

<?xml version="1.0" encoding="UTF-8" ?>
<rss version="2.0">

<channel>
  <title>Your Page</title>
  <link>https://yoursite.com/blog</link>
  <description>Name of your blog</description>
  <item>
    <title>RSS Tutorial</title>
    <link>https://somesite.com/xml/xml_rss.asp</link>
    <description>New RSS tutorial on Some Site</description>
  </item>
  <item>
    <title>XML Tutorial</title>
    <link>https://somesite.com/xml</link>
    <description>New XML tutorial on Some Site</description>
  </item>
</channel>

</rss> 

What we need to do instead is dynamically construct a feed by testing the URL “segments”. This is best understood through looking at a code example. Here is my specific implementation. FYI I've used the standard HTML comments syntax here so they stand out a bit more in the example:

{{ xml_header }}

<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <!-- Construct RSS link by testing segments -->
        <atom:link href="{{ config:app:url }}/{{ segment_1 }}{{ if segment_2 }}/{{ segment_2 }}{{ /if }}{{ if segment_3 }}/{{ segment_3 }}/rss{{ /if }}" rel="self" type="application/rss+xml" />
        <title>{{ brand:name | cdata }} – {{ segment_1 | title }}{{ if segment_3 }} / {{ segment_3 | title }}{{ /if }}</title>
        <link>{{ config:app:url }}/{{ segment_1 }}</link>
        <description>{{ if segment_1 == 'blog' }}Thoughts and tutorials about web design.{{ /if }}</description>
        <language>en</language>
        <generator>{{ config:app:url }}</generator>

        {{# BLOG
        =================================================== #}}
        <!-- Test segments to determine which collections and taxonomies to pull into the feed -->
        {{ if segment_1 == 'blog' }}
            {{ if segment_3 }}
                {{ if segment_2 == 'categories' }}
                    { collection:blog as='posts' limit='10' taxonomy:categories="{segment_3}" }
                        {{ partial:rss-post }}
                    { /collection:blog }
                {{ elseif segment_2 == 'tags' }}
                    {{ collection:blog as='posts' limit='10' taxonomy:tags="{segment_3}" }}
                        {{ partial:rss-post }}
                    {{ /collection:blog }}
                {{ /if }}
            {{ else }}
                {{ collection:blog as='posts' limit='10' }}
                    {{ partial:rss-post }}
                {{ /collection:blog }}
            {{ /if }}
        {{ /if }}

        {{# LISTENING
        =================================================== #}}
        {{ if segment_1 == 'listening' }}
            {{ collection:listening as='posts' sort="date:desc" limit='10' }}
                {{ partial:rss-post-listening }}
            {{ /collection:listening }}
        {{ /if }}

        {{# WATCHED
        =================================================== #}}
        {{ if segment_1 == 'watched' }}
            {{ collection:watched as='posts' sort="date:desc" limit='10' }}
                {{ partial:rss-post-watched }}
            {{ /collection:watched }}
        {{ /if }}
    </channel>
</rss>

In the above we're linking to partials. As an example, here is the partial that's stored in /resources/views/_rss-post.antlers.html:

{{ posts }}
    <item>
        <title>{{ title | cdata }}</title>
        <link>{{ permalink }}</link>
        <guid >{{ permalink }}</guid>
        <description>
            {{ if hero_image }}
                <![CDATA[<img src="{{ config:app:url }}{{ glide:hero_image width="1920" height="1080" }}" width="1920" height="1080" alt="{{ hero_image:alt }}" />]]>
            {{ /if }}
            {{ main_content }}
                {{ if type == 'text' }}
                    {{ text | full_urls | cdata }}
                {{ elseif type == 'super_blockquote' }}
                    <blockquote>
                        {{ quote | full_urls | cdata }}{{ if attribution }}—{{ if attribution_link }}<a href="{{ attribution_link }}">{{ /if }}{{ attribution }}{{ attribution_link ?= '</a>' }}{{ /if }}
                    </blockquote>
                {{ elseif type == 'inline_image' }}
                    {{ caption ?= '<figure>' }}
                        <![CDATA[<img src="{{ glide:image }}" loading="lazy" width="{{ image }}{{ width }}{{ /image }}" height="{{ image }}{{ height }}{{ /image }}" alt="{{ image:alt }}" />{{ if caption }}<figcaption>{{ caption }}</figcaption>{{ /if }}{{ caption ?= '</figure>' }}]]>
                {{ elseif type == 'full-width_image' }}
                    {{ caption ?= '<figure>' }}
                        <![CDATA[<img src="{{ config:app:url }}{{ glide:image width="860" dpr="2" }}" loading="lazy" width="200" height="200" alt="{{ image:alt ?? caption }}" />{{ if caption }}<figcaption>{{ caption }}</figcaption>{{ /if }}{{ caption ?= '</figure>' }}]]>
                {{ /if }}
            {{ /main_content }}
        </description>
        {{ if categories || tags }}
            {{ if categories }}
                <category>
                    {{ categories }}
                        {{ title | cdata }}
                    {{ /categories }}
                </category>
            {{ /if }}
            {{ if tags }}
                <category>
                    {{ tags }}
                        {{ title | cdata }}
                    {{ /tags }}
                </category>
            {{ /if }}
        {{ /if }}
        <pubDate>{{ date format='D, d M Y H:i:s O' }}</pubDate>
    </item>
{{ /posts }}

3. Create a Route

We need to define a route that removes the layout template and processes since we simply want to output the feed as an 'atom' content type, which is the preferred content type for RSS.

You can create routes with Statamic through routes/web.php. This is the code I have in web.php:

// e.g. /blog/rss or /listening/rss
Route::statamic('{route_dir_1}/rss', 'rss', ['layout' => '', 'content_type' => 'atom']);
// e.g. /blog/categories/business or /blog/tags/dropbox
Route::statamic('{route_dir_1}/{route_dir_2}/{route_dir_3}/rss', 'rss', ['layout' => '', 'content_type' => 'atom']);

What we're saying here is—When these routes are matched, use our 'rss' Antler's template, skip layout processing (you'll notice we have a blank layout argument), then render the content as 'atom'—which is needed for RSS.

Some other things to note:

  • We have two routes in my particular example.

  • The order of the routes matters.

  • Put less specific routes first. If the URL matches the first route, the second route will effectively be ignored.

  • You can name the variables whatever you like, I just went with {route_directory_1}, etc. because it made it easy for me to understand

  • We don't need to do it here but you can reference these route variable names in your templates if you like—although in this case, I find it easier to use the built-in Statamic's built-in {segment} tags

Tidying Up

  1. Remember to clear the route cache, else the route won't work. Use the command php artisan route:clear to do this.

    1. Also, remember to run this command on your next deployment to your production server

  2. Validate your RSS Feed using the W3C validator to check everything is OK.