RSS

Building an Atom and RSS feed generator for Statamic 3

Published June 16th, 2021

I’m one of the four people on the web who still use RSS. I use FreshRSS to collect all of my different site feeds, and consume them in Reeder. But out of the box, Statamic doesn’t include any feed capabilities.

Good thing I know how to write add-ons for Statamic 3, and this has been a great experiment in exploring how Entries work under the hood.

And better yet… you can use it too! I’ve published the full source code, and you install it with composer. Keep reading to find out more.

What does it do?

Basically this is an Atom and RSS feed generator for Statamic 3. This addon:

  • includes content from specified Collections

  • generates an Atom and RSS feed

  • is cached (including automatic clearing on save or a manual command)

  • includes an auto-discovery helper tag for your layout

  • can have its routes configured

It fits my use case: but also curious to hear how others use Atom/RSS and can always extend/explore ways to evolve this too – reach out to me on Twitter or LinkedIn (or fling me a good ol’ fashioned email).

What has this taught me?

Writing this add-on has seen me digging through the Statamic source to see how Collections and Entries are loaded (and queried). 

Basically the main controller is getting all of the Entries for the Collections specified in the configuration file, and returns a neat FeedEntry model that stores the information for each Entry in the output feed:

  • title

  • author (as a User object)

  • URI

  • summary content

  • published timestamp

  • updated timestamp

And then, this collection is sorted in descending order by the published date, and the last updated timestamp calculated, and it is passed across to a simple blade file to output in either the Atom or RSS (v2) format

Configuration

You can get up and running really quickly, with just two simple configuration options - set your title and description and, on the assumption you have a “blog” collection, everything will be good to go. 

Check out the source for full comments (and some other basic options too). But for some of the properties, some pointers to help you out:

Routes

The routes array defines the routes for the Atom and RSS feeds.

'routes' => [
    'atom' => '/feed/atom',
    'rss'  => '/feed'
],

This is the default configuration, and will give you the Atom feed at https://[yoursite]/feed/atom and the RSS feed at https://[yoursite]/feed. Pretty self-explanatory I think.

If you don’t want a specific type, simply remove it from your array. To just have an Atom feed, you could do:

'routes' => [
    'atom' => '/feed'
],

Collections

collections is simply an array of Collections to include in your feed. Nothing too special here – all entries from the Collections you specify will be merged together and sorted by publish date in descending order.

Summary

The summary option defines an ordered list of fields that will be looked for to find the contents of the <summary> element.

When content is found in a field, the remainder of the list will be skipped for that Entry. Check out the Summary section below for more details.

Author

This is a slightly complicated one given that Atom and RSS handle the author details differently – check out Author below for full details.

But overall, some basics: set author to false to not output any author information. Easy that one.

When set to an array, there are three properties you can configure:

  • handle is the field in your Blueprint that stores the author reference. Default to “author”

  • email can be “true” or “false” – when set to “true”, the author’s email address will be included in both the Atom and RSS feeds

  • name is a pattern to use to build the author’s name – by default it is set up to use the default Statamic user “name” field. Simply put, it’s a string that has square-bracketed field handles that are used to make up the Author’s name. Check out Author below for more.

Summary

When consuming feeds, I like to see an introduction or preview of the content without leaving my list of feeds.

While Atom has the <summary> element, RSS 2.0 has nothing. So this is only applicable to the Atom feed.

Given this site is a migration from an older site, and only the latest post shows the introduction field, it is missing for the majority of older posts. However, all posts do have a meta_description field. My aim was to use one, and if no content is found, use another.

The summary configuration option allows you to set an array of field handles to look at, and the controller will do so in order. When content is found in a field, that content is used, and the remaining fields in your array are skipped.

I also took this one step further and had the feed parsing the actual content of the Entry to pass to the <content> element… but decided to remove this functionality.

When you have a more complex Entry that is built with Bard sets, you need to actually process that content (without the rest of your layout). Not only does this take significant time, but also requires another layout template to process without the rest of your site’s markup.

So take note that if one of your fields uses Bard sets, you won’t get the expected output as that needs to be processed itself.

However, including the output markup means that any Sets that have custom markup and classes will not appear correctly in the RSS reader. So why include poorly formatted content when you can just as easily link and load the actual Entry? While it was a good experience to see how to parse content outside of the flow of the typical browser-side response, it also just doesn’t add enough quality value. Hence, it’s gone.

Author

The author is more complicated than it appears – Atom and RSS handle author differently, and if you change your User configuration (i.e. to use two fields for first and last name, not a single field) you need to tinker further.

In your Atom feed, you will output an <author> element, which can include <name> and if you want, <email>

Where as RSS only outputs <author> and must contain an email address.

Typically I’m one to not make my email addresses that publicly available, so the configuration option determines what is output based on the feed type and your settings.

When the Entries are collected, a reference to the Author is passed along to the FeedEntry inside a FeedEntryAuthor model.

The FeedEntryAuthor takes your configuration name template, and collects the necessary data from the Author model, and builds the name. This means you can tinker and tailor the name to suit your needs (and your User blueprint).

The purpose of this model is to compartmentalise this logic to keep the main controller cleaner. When we need the name of the Author, we now just need to call the name() function on the FeedEntryAuthor, and it will output the name in the format as configured. Easy.

Then, in each template – for Atom or RSS – includes (or excludes) the email address information, based on your configuration (and the assumption that the handle is still “email”).

Performance and caching

Compared to public page hits, the feeds themselves may not be accessed as often… but still, why not make them performant too? 

I have gone for a fairly aggressive caching strategy. Basically, cache forever.

Wait, wait, wait, forever?

You were gone forever... I counted
Oscar was always worried we'd be gone forever...

Yep, forever.

But… the addon is also listening to your Entry saves. When the EventSaved event is fired, the addon will check if the Entry’s Collection is one of those configured for the feeds, and if so, will clear the Entry cache.

If you’re impatient and need to invalidate it, you can also do that by asking nicely:

php please rss-cache:clear

Auto-discovery

It’s great to add a site to a reader and have it just know what to do – this is due to the auto-discovery markup in your site itself.

It’s pretty easy to do yourself: just add some <link> tags that describe where your feeds live.

But wouldn’t it be handy if this could automagically be done based on your configuration? Sure! So I created a Tag that does this.

All you need to do is put the Tag in your <head>, and it will output the necessary <link> tags for your Atom and RSS feeds:

{{ rss_auto_discover }}

This way, if you were to change your configuration, your auto-discover links get updated too. Magic, hey?

Use it yourself

You can check out the full source code at the mitydigital/statamic-rss-feed Github repository – make sure you check out the readme for full setup instructions. But basically: 

composer require mitydigital/statamic-rss-feed

Then publish the config file:

php artisan vendor:publish --provider="MityDigital\StatamicRssFeed\ServiceProvider" --tag=config

Check the config/statamic/rss.php file for full configuration options – you’ll need to set the ‘name’ and ‘description’ properties, and you may need to tweak others depending on your Blueprint setup.


Hopefully you find this useful – and I know I can now easily add an Atom and RSS feed to any of my Statamic sites with ease. Let me know how you go – I’d love to hear your thoughts!

You may be interested in...