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

Podcastfeed Laravel Package

torann/podcastfeed

Generate podcast RSS feeds in Laravel with torann/podcastfeed. Build valid iTunes-compatible feeds, add episodes, enclosures, artwork, and metadata, then output them as XML for Apple Podcasts and other directories.

View on GitHub
Deep Wiki
Context7

Getting Started

Confirm the framework constraint before you write a line of code. composer.json pins this to Laravel 5.x. If the target app is on Laravel 6+ or PHP 8+, stop — the package won't resolve and the upstream is dead. On Laravel 5.5+ the service provider auto-discovers; on earlier 5.x register Torann\PodcastFeed\PodcastFeedServiceProvider in config/app.php and alias the PodcastFeed facade.

Read three files, in this order:

  1. composer.json — the constraint that decides whether you proceed at all.
  2. src/PodcastFeedServiceProvider.php — what gets bound, what's publishable, and whether the facade resolves to a singleton or a fresh builder. That last detail governs whether you can safely build multiple feeds in one request lifecycle.
  3. config/podcastfeed.php after php artisan vendor:publish --provider="Torann\PodcastFeed\PodcastFeedServiceProvider" — the channel-level metadata surface (title, link, description, language, author, image, category, explicit). Treat this file as the single source of channel truth.

The builder class is small; its method names are the documentation. Skim addItem/set* to learn the exact iTunes tag vocabulary you'll be working with.

First use case — one route, one show. Resolve the builder from the facade, seed it with channel config, loop your episode model into addItem(), render:

Route::get('feed', function () {
    $feed = PodcastFeed::create(config('podcastfeed.channel'));

    Episode::published()->latest('published_at')->get()
        ->each(fn ($e) => $feed->addItem($e->toFeedItem()));

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

Load it in a browser, then immediately run it through Apple's Podcasts validator and xmllint --noout. A green feed on day one is the entire point; everything after that is polish.

Implementation Patterns

Put the feed mapping on the model, not the route. Add a toFeedItem(): array (or attribute cast) to Episode. The route stays a two-liner, the contract becomes unit-testable without HTTP, and the mapping travels with you if you later replace the package.

public function toFeedItem(): array
{
    return [
        'title'       => $this->title,
        'description' => $this->show_notes,          // may contain HTML
        'pubDate'     => $this->published_at->toRfc2822String(),
        'guid'        => 'episode-'.$this->id,        // stable forever
        'author'      => $this->host_name,
        'duration'    => $this->duration_hms,         // itunes:duration
        'explicit'    => $this->explicit ? 'yes' : 'no',
        'enclosure'   => [
            'url'    => $this->audio_url,
            'length' => Storage::disk('s3')->size($this->audio_path),
            'type'   => 'audio/mpeg',
        ],
    ];
}

Cache the rendered string, not the query. Podcast clients poll aggressively and back off badly. Cache::remember('feed:'.$show->slug, 300, fn () => $feed->render()), keyed off $show->episodes()->max('updated_at') for invalidation. Render once, serve many.

Conditional GET from day one. Emit ETag/Last-Modified from the newest episode timestamp and return 304 on match. Cheaper than caching and it's what well-behaved clients expect.

Serve one feed to every directory. No user-agent branching, no /feed/apple vs /feed/spotify. Divergent feeds create cache-invalidation and "why does it look wrong in Overcast" debugging sessions you won't enjoy.

Multi-show. Parameterize the route on a show slug, resolve channel config per show, keep the presenter identical. The only per-show difference should be metadata — never structure.

Test the shape, not the library. Assert HTTP 200, correct content type, well-formed document (load with DOMDocument in the test), and presence of itunes:author, a leaf-level itunes:category, itunes:image, itunes:explicit, plus per-item guid/enclosure/pubDate. That suite is the artifact that survives a package swap.

Gotchas and Tips

The Laravel 5 constraint is the deal-breaker, not the code quality. On 6+, expect removed helpers and dynamic-property deprecations under PHP 8.2. With zero dependents, nobody upstream is exercising this code — you become the maintainer the moment you require it. The pragmatic path: read src/ as a spec reference and own a ~300-line renderer.

enclosure length must be the byte size, not a formatted string like "45 MB". Get it wrong and Apple accepts the feed but clients show a broken or unplayable episode.

guid must be stable and URL-independent. Use episode-{id} with isPermaLink="false". If you ever re-host audio or swap CDN domains, URL-derived GUIDs force every subscriber to re-download every episode.

pubDate is RFC 2822. Carbon's toRfc2822String() — not toIso8601String(), not ->format('Y-m-d'). Emit UTC consistently.

itunes:category must be a leaf. A top-level Technology alone is rejected; it needs its subcategory. Single most common validator failure.

Description escaping is where the XML breaks. Show notes with HTML either break the document or render as literal tags. If the builder doesn't CDATA-wrap for you, sanitize and pre-escape at the presenter layer. XMLWriter::writeCdata() is safer than string concatenation.

Blade + <?xml prologue. Any whitespace, BOM, or trailing newline before the declaration yields an XML parse error that renders fine in a browser but fails in every reader. Ensure the first byte of the response body is <.

Config cache. php artisan config:cache after publishing, or channel metadata edits appear to do nothing in production while working locally. Classic half-hour lost.

itunes:explicit accepts yes/no/clean — not booleans. Cast at the presenter.

Artwork: square, 1400×1400 minimum (3000×3000 recommended), PNG/JPEG, HTTPS with no redirect chain. Directories fetch on submission; a redirect or Content-Type mismatch fails validation silently.

Don't rely on it for tags it predates. itunes:episodeType, itunes:season/itunes:episode, podcast:transcript, podcast:chapters, podcast:funding, podcast:locked are all post-Laravel-5. The custom-namespace/raw-extension escape hatches exist, but plan to own that XML.

Validate in CI. Assert 200, content type, well-formed XML, and the required tag set on every deploy. Feed regressions don't throw — they surface days later when a directory quietly re-crawls and an episode vanishes.

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.
abbasudo/artisan-route
mirvan/ippanel
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package