Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Feed Laravel Package

roumen/feed

A Laravel package for generating RSS and Atom feeds from application data. Build custom feeds with a fluent API, caching, and clean XML output for blogs, news, podcasts, and other dynamic content.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install with Composer, pinning at least the current patch release:
    composer require roumen/feed:^1.0.3
    
    v1.0.3 is a security patch — it escapes CDATA terminators to prevent XML injection. Anything rendering user-supplied content into a feed should be on >=1.0.3.
  • Publish the package config and views so you can see what the feed actually renders:
    php artisan vendor:publish --provider="Roumen\Feed\FeedServiceProvider"
    
  • If package auto-discovery is not working for your Laravel version, register the provider in config/app.php and add the Feed alias if the package exposes one. Then run:
    php artisan package:discover
    php artisan config:clear
    
  • Look first at:
    • config/feed.php for defaults such as format, cache, and view paths.
    • resources/views/vendor/feed/rss.blade.php and atom.blade.php after publishing, because most output quirks are view quirks — and because published views are where you can reintroduce the injection bug the patch just fixed.
    • The package Feed class for the exact add(), render(), setCache(), setShortening(), and setTextLimit() signatures in your installed version.
  • First use case: add a /feed route to a blog or news site. Build the feed from the latest published posts, return RSS, and link to it from your layout:
    Route::get('/feed', FeedController::class)->name('feed');
    

A minimal controller:

public function __invoke()
{
    $feed = app('feed');

    $feed->title = config('app.name').' Blog';
    $feed->description = 'Latest articles';
    $feed->link = route('feed');
    $feed->lang = app()->getLocale();
    $feed->setDateFormat('datetime');

    $posts = Post::with('author')->latest('published_at')->limit(20)->get();

    $feed->pubdate = $posts->first()?->published_at ?? now();

    foreach ($posts as $post) {
        $feed->add(
            $post->title,
            $post->author->name,
            route('posts.show', $post),
            $post->published_at,
            $post->excerpt,
            $post->body
        );
    }

    return $feed->render('rss');
}

Implementation Patterns

Use one controller or builder per feed. Keep feed construction out of routes and models. A dedicated FeedController or FeedBuilder service makes it easy to test the XML, reuse metadata, and swap the package later.

Cache the rendered feed, not the models. The package supports feed caching; pair it with a key that includes the feed type, locale, tenant, and category:

$feed->setCache(10, 'feed:blog:'.app()->getLocale());

if (! $feed->isCached()) {
    $posts = Post::with('author')->latest('published_at')->limit(20)->get();

    foreach ($posts as $post) {
        $feed->add(
            $post->title,
            $post->author->name,
            route('posts.show', $post),
            $post->published_at,
            $post->excerpt,
            $post->body
        );
    }
}

return $feed->render('rss');

Invalidate on content changes. Add model events or an observer that clears the feed cache when a post is published, updated, unpublished, or deleted:

static::saved(fn () => Cache::forget('feed:blog:en'));
static::deleted(fn () => Cache::forget('feed:blog:en'));

For multi-locale or multi-tenant feeds, version the cache key instead of trying to forget every variation:

Cache::forget('feed:blog:en:v2');

Separate RSS and Atom cache keys. Do not let render('rss') and render('atom') share a cache key. Their XML structures and date formats differ.

Use absolute URLs. Feed readers do not resolve relative links reliably. Use route() or url() for item links, the feed link, and images.

Eager load and limit. Feed readers poll often. Load at most 20–50 items, eager load relationships such as authors, and avoid queries inside the item loop.

Customize through published views. Most day-to-day customization is easier in the published Blade views than by modifying package code: add <enclosure> for podcast episodes, <category> for tags, media RSS namespaces, or fuller HTML content. Keep the XML declaration as the first bytes of the file.

Let the package do the escaping — don't pre-escape. Since v1.0.3 the package neutralizes ]]> inside CDATA sections, so pass raw text ($post->title, $post->body) and let it handle output encoding. If you hand-roll <![CDATA[ ... ]]> blocks in a published view, you opt out of that protection and own the sanitization yourself.

Sanitize rich HTML before it reaches the feed. The CDATA fix prevents structural XML breakage, not stored XSS or unwanted markup in readers. Run post bodies through an HTML sanitizer (e.g. mews/purifier) and strip scripts/iframes before add().

Wrap add() in a helper if you call it often. The positional signature is easy to mix up:

$feed->add($title, $author, $link, $date, $description, $content);

A small addPostToFeed($feed, $post) helper avoids passing excerpt and body in the wrong order.

Return the correct content type. If render() returns a string in your installed version, wrap it explicitly:

return response($feed->render('rss'), 200, [
    'Content-Type' => 'application/rss+xml; charset=UTF-8',
]);

If the package returns a response object already, return it directly and verify the header in a browser or with curl -I.

Gotchas and Tips

  • Upgrade to >=1.0.3 if anything user-authored lands in the feed. Before this patch, a title, excerpt, or body containing ]]> could terminate the CDATA block early and inject arbitrary XML into your feed — a real vector for anyone allowed to post comments, listings, or profile text. After upgrading, composer update roumen/feed and re-run your feed test.
  • The patch does not cover content you escape manually. Published views copied from an older install still contain the old CDATA plumbing. Diff your resources/views/vendor/feed/*.blade.php against the shipped views on upgrade, or delete and re-publish them.
  • Caching can make local development confusing. A feed cached for 10 minutes will not reflect your latest Blade or query changes. Disable package caching in local, use a very short TTL, or clear the relevant cache key after changes. A stale cache will also keep serving pre-patch XML — flush after upgrading.
  • Blade whitespace breaks XML. A blank line before <?xml version="1.0" encoding="UTF-8"?> makes the feed invalid. Check the published view starts with the XML declaration at byte zero.
  • Escaping differs between RSS and Atom. Descriptions and content may be escaped by Blade. If you need HTML inside a feed, sanitize it and use CDATA deliberately in the view; do not double-escape &, <, and >.
  • Date formats matter. RSS expects RFC 2822-style dates; Atom expects ISO 8601. Test the rendered feed with a validator rather than assuming setDateFormat() handles every case.
  • Empty pubdate values produce warnings. Set a fallback date, usually the latest item date or now(), so validators and readers have something to work with.
  • setShortening() and setTextLimit() can cut UTF-8 or HTML mid-tag. They truncate by character count, not by DOM node — a truncation applied before CDATA wrapping can leave an unclosed <p> or a split entity. Test with non-English content and HTML excerpts. For full-content feeds, disable shortening and control the content yourself.
  • Cache keys collide easily. Include feed name, locale, category, tenant, and format in the key. Never share a cache entry between public and authenticated/personalized feeds.
  • Stale feeds are a cache-invalidation problem, not a package problem. Clear the feed cache on publish/unpublish events and in queue workers after imports.
  • Validate the output, and add a regression test for the injection fix. Use the W3C Feed Validation Service and xmllint --noout feed.xml, and keep an assertion that a title containing ]]> still produces well-formed XML:
    $post->update(['title' => 'Hack ]]> <script>alert(1)</script>']);
    $this->get('/feed')->assertSee(']]]]><![CDATA[>', false);
    
  • Package compatibility is worth checking before adoption. With zero dependents listed, treat this as a small, focused dependency: verify the Laravel version constraint, keep feed generation behind a service class, and add an integration test that asserts the XML contains expected item titles and links.
  • Extension point: subclass or decorate the package Feed class in your own service if you need organization-specific defaults, then bind it in the container. That keeps package-specific calls isolated and makes a future migration to native XML or another feed package much cheaper — and means a future security patch only has to be applied in one place.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin
skyraptor/chatter
edlugz/sasapay
abbasudo/artisan-route