Create custom filtered RSS feeds in Ghost, the easy way
Create custom feeds, on custom routes, without editing theme files
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.