Adding RSS and Atom Feeds to Your Laravel App or Website

Saturday, February 18th 2023 | Development
Heads Up
This post is over a year old and might not be up-to-date with currently available information.

What is RSS and Atom

RSS, or Really Simple Syndication is a documented format for sharing web content in the form of a "feed" - hence why they're sometimes referred to as either "RSS Feed" or "Atom Feed".

An RSS feed URL is presented to an application that can extract elements such as the content title, body and author. For example, you can configure your blog to use RSS feeds to automatically distribute your newly added content to those who have subscribed to the feed URL. The benefit of which is that if the "subscriber" is another platform, such as those that automatically post to social media platforms - posts can be made to those platforms to notify a wider audience.

Atom was created as an alternative to RSS to not only improve upon the standard, but to also alleviate some of the limitations of RSS. Both Atom and RSS use XML to describe their feed contents.

Adding RSS and Atom To Your Laravel App or Website

For this post, I'll be referring to laravel-syndication at version 1.2, a package I wrote for use with Laravel and creating RSS and Atom feeds.

First things first, you'll need to install the package - the easiest way to do this is using composer.

composer require mykemeynell/laravel-syndication

You only have to do this next part if you don't have auto-discovery enabled within your Laravel app; and that is to add the service provider to the config/app.php file;

'providers' => [
// ...
LaravelSyndication\LaravelSyndicationServiceProvider::class,
// ...
],

Next, you'll need to publish the configuration, this is where you'll actually tell the package which models should be used to create feeds;

php artisan vendor:publish --tag=laravel-syndication

And that's it, the package is installed and ready to start serving your RSS and Atom feeds for you.

Use case example

In this example, we'll be serving both an Atom and RSS feed for our hypothetical blog.

Creating the feeds

The quickest and easiest way to create the files needed, would be to use artisan, like so;

php artisan make:feed Posts

At which point, we will have a new file created under app/Feeds/ called Posts.php.

namespace App\Feeds;
 
class Post extends RssAndAtomFeed
{
public function setUp(): void
{
// .
}
 
function filter(Builder $builder): Builder
{
return $builder
->whereNotNull('published_at');
}
}

You'll see that by default, our feeds class extends the RssAndAtomFeed abstract class, so it will in turn create one of each; if you only wanted to create an RSS feed you would instead extend RSSFeed, the same goes for AtomFeed.

The filter() method is called when the feed is fetching the entities that should be published within the feed, by default it uses a published_at field, this could be any criteria you wish that would mean your model entity is considered "published". Normally, the same criteria that would mean your user would consume your content, rather than receive a 404 error.

The setUp() method is the one we'll focus on next, now we need to specify which model the feed should use, and what information should be associated at the top level within our feed;

namespace App\Feeds;
 
class Podcast extends RssAndAtomFeed
{
public function setUp(): void
{
$this->model(\App\Models\Post::class)
->title("Awesome Blog")
->description("An amazing blog")
->url(route("blog"))
->language("en")
->updated($this->lastCachedAt());
}
 
function filter(Builder $builder): Builder
{
return $builder
->whereNotNull('published_at');
}
}

Code breakdown;

  • model(\App\Models\Post::class) - the model that is used when generating the feed.
  • title("Awesome blog") - our feeds name.
  • description("An amazing blog") - a description of our feed.
  • url(route("blog")) - the URL of our feed.
  • language("en") - the language that our feed is served in.
  • updated($this->lastCachedAt()) - a timestamp for when our feed was last updated, in this case because we're allowing the feed to be cached - we'll just use the timestamp of when the feed was cached last.

Configuring the model

This is pretty much the last step - aside from adding any meta tags to your website that you might want; and it's as simple as the last one. All we need to do, is tell our model which fields go where within our feed.

Firstly, our model needs to implement the IsSyndicationItem interface; this will specify a method that is used within the package that creates a feed entry from our model. So our model might look like this;

namespace App\Models;
 
use Illuminate\Database\Eloquent\Model;
use LaravelSyndication\Contracts\Models\IsSyndicationItem;
use LaravelSyndication\Feeds\FeedItem;
 
class Post extends Model implements IsSyndicationItem
{
function toFeedItem(): FeedItem
{
return (new FeedItem())
->id(route('blog.read', $this->slug))
->title($this->seo_title ?? $this->title)
->description($this->meta_description ?? $this->excerpt)
->content(Str::markdown($this->body))
->url(route('blog.read', $this->slug))
->author(
new Person(name: $this->authorId->name, email: $this->authorId->email),
)
->updated($this->updated_at)
->published($this->published_at)
->category($this->category->name);
}
}

Lets break it down;

As we mentioned before, our model needs to implement the IsSyndicationItem interface; this will be checked within the laravel-syndication package. At which point, we'll also need to specify the toFeedItem method, which is expected to return a LaravelSyndication\Feeds\FeedItem object.

The methods that are called on the instantiated object are used to map the data we hold within our model entity, to the fields that are part of our feeds; if you'd like to read more about which fields are required or optional; here is a little more information on Atom and RSS feeds. There is also a validation service offered by W3 for both types of feed.

Adding the meta tags

Within the <head> tags of your HTML, you can specify the location of your generated feeds; either all of them, or ones you specify;

<head>
<!-- Output all feeds -->
{!! LaravelSyndication::meta() !!}
</head>
<head>
<!-- Output specifically the 'blog' feed that we have created -->
{!! LaravelSyndication::meta('blog') !!}
</head>

All done

That's it! Of course there is more configuration you can do within this package such as specifying how long to cache feeds for, which feeds should be cached - if any at all, and adding additional fields to feeds. If you'd like to find out more of what you can do with this package then you can checkout the readme that ships with it.