The Autodidacts

Exploring the universe from the inside out

Create custom filtered RSS feeds in Ghost, the easy way

Create custom feeds, on custom routes, without editing theme files

Note: this post is part of #100DaysToOffload, a challenge to publish 100 posts in 365 days. These posts are generally shorter and less polished than our normal posts; expect typos and unfiltered thoughts! View more posts in this series.

When I started 100 days to offload, and mentioned that readers could filter out posts by tag in their feed reader, an RSS subscriber wrote in suggesting that I create custom feeds instead. That way, readers could keep the longform posts in their read-every-post feed, while relegating the firehose RSS feed to a lower-priority section of their inbox.

I have now done this. Here is the feed for everything other than #100DaysToOffload posts:

https://www.autodidacts.io/filter-100daystooffload/rss/ (I may not maintain this route after I finish the challenge, so you’ll want to switch back to https://www.autodidacts.io/rss/ (the everything feed) at that point.)

https://www.autodidacts.io/rss/ is everything.

https://www.autodidacts.io/tag/100daystooffload/rss/ is just #100DaysToOffload posts.


First, I did it the hard way. After reading on the forum and the docs, I used routes.yaml and a custom rss.hbs in the root directory of my theme.

I added the following to routes.yaml:

routes:
  /filter-100daystooffload/rss/:
    template: rss
    content_type: text/xml

And added an rss.hbs template with the following contents:

<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
<channel>
<title><![CDATA[ {{@site.title}} ]]></title>
<description><![CDATA[ {{@site.description}} ]]></description>
<link>{{@site.url}}</link>
<image>
    <url>{{@site.url}}/favicon.png</url>
    <title>{{@site.title}}</title>
    <link>{{@site.url}}</link>
</image>
<lastBuildDate>{{date format="ddd, DD MMM YYYY HH:mm:ss ZZ"}}</lastBuildDate>
<atom:link href="{{@site.url}}" rel="self" type="application/rss+xml"/
<ttl>60</ttl>
{{#get "posts" limit="100" filter="tags:-100daystooffload" include="authors,tags"}}
    {{#foreach posts}}
    <item>
        <title><![CDATA[ {{title}} ]]></title>
        <description><![CDATA[ {{excerpt}} ]]></description>
        <link>{{url absolute="true"}}</link>
        <guid isPermaLink="false">{{id}}</guid>
        <category><![CDATA[ {{primary_tag.name}} ]]></category>
        <dc:creator><![CDATA[ {{primary_author.name}} ]]></dc:creator>
        <pubDate>{{date format="ddd, DD MMM YYYY HH:mm:ss ZZ"}}</pubDate>
        <media:content url="{{feature_image}}" medium="image"/
        <content:encoded><![CDATA[ {{content}} ]]></content:encoded>
    </item>
    {{/foreach}}
{{/get}}

</channel>
</rss>

The only line I had to change, depending on what we want to filter out, was this:

{{#get "posts" limit="100" filter="tags:-100daystooffload" include="authors,tags"}}

(Note: limit=”all” is unfortunately depreciated. But feed readers hit /rss endpoints frequently, and lowering the limit reduces server load, so it’s not a huge deal.)

Note, also, that the default RSS feeds are generated with code: there’s no rss.hbs in Ghost core that I could copy. This version has a few small differences from the default RSS template, but works fine.


I didn’t like this approach, for several reasons. Mainly, the filter is hardcoded, so the template can’t be re-used for multiple custom feeds. And if Ghost updates the default template when a new version of the RSS spec comes out in 2200 AD, this will still be RSS 2.0. It feels janky.

I tried using a collection, but that didn’t work. But, lo! Channels have RSS feeds by default, and can be filtered!

In the end, all that was needed was the following in my routes.yaml:

routes:
  /filter-100daystooffload/:
    controller: channel
    filter: tag:-[100daystooffload]

And as a bonus (assuming you want it), there’s a homepage view as well as the RSS feed.

Along the way, before hitting on this 4-lines-of-text solution, I managed to create the world’s least valid text/xml RSS feed — the “everything everywhere all at once” edition — with every piece of custom CSS and JS from every post applied at once, including typesetting the whole thing in Comic Sans. It was glorious.

Sign up for updates

Join the newsletter for curious and thoughtful people.
No Thanks

Great! Check your inbox and click the link to confirm your subscription.