cable8mm/xeed
Xeed generates Laravel database seeders from Excel spreadsheets. Map worksheets to tables, define columns and relationships, then produce ready-to-run seeder classes so bulk data stays version-controlled and reloads consistently across environments.
cable8mm/xeed generates RSS/Atom feeds straight from Eloquent models — the appeal is that you describe a feed as a class instead of hand-assembling XML.
Install and publish
composer require cable8mm/xeed
php artisan vendor:publish --tag=xeed-config # config/xeed.php
Check composer.json constraints first — v2.0.2 is a patch on the 2.x line, so if you are still on 1.x the breaking surface lives in the 2.0.0 major, not here.
Where to look first
README.md (the only thing that changed in 2.0.2 besides internal refactoring) for the current generator signature.config/xeed.php for default format, cache TTL, and route prefix.src/Feed.php (or equivalent base class) after the "refactoring: all codes" commit — the public surface should be unchanged, but confirm the base class methods your feeds override (items(), toFeedItem(), format/view properties) still exist with the same signatures.First feed, end to end
php artisan make:feed PostFeed
// app/Feeds/PostFeed.php
class PostFeed extends Feed
{
protected $model = Post::class;
protected $format = 'atom'; // or 'rss'
protected $limit = 50;
public function toFeedItem(Post $post): FeedItem
{
return FeedItem::create()
->id($post->id)
->title($post->title)
->summary($post->excerpt)
->updated($post->updated_at)
->link(route('posts.show', $post))
->authorName($post->author->name);
}
}
// routes/web.php
Route::feeds(); // registers the published feeds
Verify before trusting it — hit /feed/post (or whatever prefix your config uses), check the Content-Type header is application/rss+xml / application/atom+xml, and paste the URL into the W3C Feed Validator. Do this once at setup; it catches the 90% of feed bugs that are date-format or escaping problems.
First real use case: a blog or changelog feed for a Post model with a published_at scope — small, verifiable, and exercises ordering, limits, and date handling in one go.
One feed class per collection, not per route. Feeds are cheap to declare; a PostFeed, NewsFeed, PodcastFeed each get their own class and their own URL. This keeps ordering/filtering logic in one place and lets you tune $limit per feed.
Filter at the query, not in the mapping. Override the items query so the feed reflects only published content:
protected function items(): Collection
{
return Post::query()
->whereNotNull('published_at')
->where('published_at', '<=', now())
->latest('published_at')
->limit($this->limit)
->with('author') // eager-load anything toFeedItem touches
->get();
}
The with() matters: a feed of 50 items without eager loading is 50+ extra queries per request.
Treat feeds as cached HTTP responses. Feeds are read by crawlers far more often than by humans. Wrap them in your normal cache layer (Cache::remember around the query, or the package's TTL config) and set a sane Cache-Control. A 60-second TTL plus a updated_at-keyed invalidation covers most sites.
Scheduling pattern for expensive feeds. If a feed aggregates across models, generate the XML on a schedule into a static file or cache key rather than rebuilding on every request:
Schedule::call(fn () => Cache::put('feed.posts', PostFeed::render(), now()->addHour()))->hourly();
Integration tips
<link rel="alternate" type="application/rss+xml" href="..."> in your layout <head> — this is what makes browsers and readers discover the feed.sitemap.xml generation and to your robots.txt/Search Console if discovery matters.FeedItem id() stable and globally unique (a permalink or tag: URI is ideal); changing it re-surfaces every item as new in readers.$this->get('/feed/posts')->assertHeader('Content-Type', 'application/atom+xml; charset=UTF-8') plus an assertSee on a known title. Snapshot the XML if you want regressions to be visible in diffs.Upgrading within 2.x. v2.0.2 is "refactoring: all codes" plus a README icon fix — no stated feature or deprecation. Pin ^2.0.2 deliberately, then diff the generated feed XML against what 2.0.1 produced for the same data. Byte-identical (or semantically identical) output means the refactor is safe for you; anything else is worth a bug report.
D, d M Y H:i:s O), Atom wants RFC 3339/ISO 8601. Timezones must be explicit — a Carbon instance without ->tz() serialized in UTC is fine, but published_at stored as a naive local datetime will render wrong offsets. Set APP_TIMEZONE correctly and convert on output.&, <, or emoji will break hand-rolled XML but should be escaped by the package's builder. If you inject raw HTML into a content field, do it via the package's content method, not string concatenation — and remember readers sanitize aggressively, so inline styles and scripts are wasted bytes.Route::feeds() registers wildcard-ish routes; if you have a catch-all /{slug} route defined earlier, feeds can 404 or render your page instead. Register feed routes before catch-alls and check php artisan route:list | grep feed.Post::saved/deleted to forget the feed cache key, or you will ship stale feeds for the full TTL and get complaints from aggregators.updated on one item can invalidate an entire Atom document.Feed and override the item query or the view rather than editing vendor code; publish the feed view if you need custom XML elements (<enclosure> for podcasts, <media:content> for images). Vendor-published overrides survive patch releases — direct edits do not./#109) is the real release note for anything touching serialization.How can I help you explore Laravel packages today?