
Short answer: A CodeIgniter 4 RSS feed needs three small pieces. Add a route such as $routes->get('rss.xml', 'Feed::index'), create a controller that loads the newest published posts from your model, and build RSS 2.0 with XMLWriter, which escapes text for you. Return it with the content type application/rss+xml, absolute links from site_url() and a stable GUID based on the post ID. Then connect the feed address to an auto-poster, and every new post is shared on social media automatically.
CodeIgniter 4 is a lightweight PHP framework known for its small footprint, clear structure and easy hosting on ordinary shared servers. Many company sites, directories and small blogs run on it. The framework has no feed component, and you do not need an extra package: PHP already contains the XML tools, and CodeIgniter provides routing, models and a response object. This guide walks through the route, the controller, the model query, images, dates, caching and testing, and explains the mistakes that most often break a feed.
A feed is a list of the newest public items with five facts for each: a title, a link, a unique ID, a date and a short description. A picture is a welcome sixth. Check that your database table has these pieces. A typical posts table has id, title, slug, summary, image, status y published_at. If you only have a full body column, add a short summary column; auto-posters and readers display the description, and a whole article makes a poor social post. The reasoning is in how to control the excerpt in your RSS feed.
Also confirm that baseURL in app/Config/App.php, or app.baseURL in your .env file, is your full public address with https. Every link in the feed is built from it, and a wrong value produces links to localhost or to the wrong host.
Open app/Config/Routes.php and add:
$routes->get('rss.xml', 'Feed::index');A route with a file-like name works as expected, and the feed is then available at https://www.example.com/rss.xml. If you prefer a path without an extension, such as /feed, use that name in the route instead. Pick one address and keep it, because auto-posters store it.
Create app/Controllers/Feed.php. The code below uses the built-in XMLWriter, which escapes ampersands and angle brackets in text automatically, so a title such as “Q&A” cannot break the document:
<?php
namespace App\Controllers;
use App\Models\PostModel;
use XMLWriter;
class Feed extends BaseController
{
public function index()
{
$posts = (new PostModel())
->where('status', 'published')
->orderBy('published_at', 'DESC')
->findAll(20);
$xml = new XMLWriter();
$xml->openMemory();
$xml->startDocument('1.0', 'UTF-8');
$xml->startElement('rss');
$xml->writeAttribute('version', '2.0');
$xml->startElement('channel');
$xml->writeElement('title', 'Example Blog');
$xml->writeElement('link', site_url('blog'));
$xml->writeElement('description', 'News and articles from Example');
foreach ($posts as $post) {
$xml->startElement('item');
$xml->writeElement('title', $post['title']);
$xml->writeElement('link', site_url('blog/' . $post['slug']));
$xml->startElement('guid');
$xml->writeAttribute('isPermaLink', 'false');
$xml->text('post-' . $post['id']);
$xml->endElement();
$xml->writeElement('pubDate', gmdate(DATE_RSS, strtotime($post['published_at'])));
$xml->writeElement('description', $post['summary']);
if (! empty($post['image'])) {
$xml->startElement('enclosure');
$xml->writeAttribute('url', base_url($post['image']));
$xml->writeAttribute('type', 'image/jpeg');
$xml->writeAttribute('length', '0');
$xml->endElement();
}
$xml->endElement();
}
$xml->endElement();
$xml->endElement();
$xml->endDocument();
return $this->response
->setHeader('Cache-Control', 'public, max-age=300')
->setContentType('application/rss+xml', 'utf-8')
->setBody($xml->outputMemory());
}
}The model is the usual CodeIgniter model: a class PostModel in app/Models that extends CodeIgniter\Model, with $table = 'posts', a primary key and the allowed fields. Nothing in it is specific to feeds.
The query. It filters on the status and sorts by the publication date, newest first, with a limit of 20. A feed does not need the whole archive, and a short one is faster to download and to check. Sorting by an update date would push an edited old post to the top, and an auto-poster might treat it as new.
The GUID. The ID in post-12 never changes, even if the slug is edited. If the GUID were the slug or the address, a small correction could make the item look new, and it could be shared twice; see why your RSS feed auto-posts the same article twice.
The date. gmdate(DATE_RSS, ...) formats the time in the pattern that RSS expects. If the database stores local time, convert it to UTC first; mistakes in this step shift every post by hours. The common problems are listed in RSS date formats and pubDate errors.
The links. site_url() y base_url() produce absolute addresses from your configured base URL. Relative paths work in a browser on your site but break in readers and on social networks, as explained in relative URLs in RSS feeds.
Social networks build their preview cards from the picture of an item, so the image address matters. Store a path such as uploads/posts/cover.jpg and turn it into a complete address with base_url(). Use images of about 1200 pixels wide and make sure the folder is publicly readable. The type attribute should match the real file type; if your uploads can be JPEG, PNG or WebP, derive the type from the file extension instead of writing JPEG every time. Recommended dimensions for each network are listed in social media image sizes.
Readers and auto-posters request the feed regularly, and the data changes rarely. The cache header in the example lets proxies keep the response for five minutes. For busy sites you can also store the finished XML with the framework’s cache service and delete it when a post is saved, so the database is not queried on every request. Keep the lifetime short; a feed cached for a day delays every share by a day. If your server sits behind a security service, make sure it does not block the feed address; the symptoms are described in fixing 403 errors on RSS feeds.
Add a link tag to the head of your layout so that browsers and readers can find the feed: <link rel="alternate" type="application/rss+xml" title="Example Blog" href="https://www.example.com/rss.xml">. In your real layout, build the address with site_url('rss.xml'). A visible link in the footer, labelled RSS, helps visitors too. Discovery does not replace pasting the address into an auto-poster, but it makes the feed easy to find.
If your description contains HTML, for example an intro paragraph with links, use $xml->startElement('description'), then $xml->writeCdata($html), then $xml->endElement(). A CDATA section keeps the markup intact without double escaping; the trade-offs are discussed in CDATA and HTML in RSS descriptions. For auto-posting, however, plain text is almost always better, because social networks show the text without formatting.
You can also add a category element for each post, using the name of the category or tag. Auto-posters can turn categories into hashtags or use them to route items, so a clean set of short, consistent category names pays off. Write $xml->writeElement('category', $post['category']) once per category, and keep the names stable: renaming a category later changes the hashtags of future posts.
A real site often has more than one kind of content: news, tutorials, product updates. Add a route that contains the (:segment) placeholder, for example rss/(:segment).xml, pointing to a second controller method, which receives the section name as an argument. The method that receives the section name and adds a where('section', $section) condition. Return a 404 response when the section does not exist, so crawlers do not index empty feeds. Move the XML building into a private method that accepts a list of posts and a channel title, so both methods share it. Separate feeds let you send tutorials to LinkedIn, product updates to a customer channel and news to Facebook. Never place the section name straight into a query string by hand; pass it through the query builder, which binds the value safely. The idea of splitting feeds is explained in category filtering for auto-posting.
Some developers prefer a view file for the XML, like the other pages of the application. It is possible, but it has traps: PHP short open tags and the XML declaration collide, whitespace before the declaration breaks the document, and every value must be escaped with a function such as htmlspecialchars with the XML flags. XMLWriter avoids all three problems, which is why it is used here. If you do use a view, return it with $this->response->setContentType('application/rss+xml')->setBody(view('feed', $data)) and test the output with a validator.
curl -i and confirm that the status is 200 and the content type is application/rss+xml.Routing, controllers and the response object are described in the CodeIgniter user guide. For more checks, see debugging an RSS feed with curl.
PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your rss.xml route, connect your accounts and choose how each post is built from the title, the description or the page title. PostRSS checks the feed as often as every minute and shares each new post, with its image and link, to the channels you choose among 66 supported networks, messengers, team chats and blogs, including Facebook Pages, LinkedIn, X, Telegram and Bluesky. Keyword filters, UTM parameters, hashtags from categories and posting windows let you tune each channel, and the REST API and webhooks connect PostRSS to your own systems. See the PostRSS features page and the plan limits on the pricing page. More developer guides are under the Technical Guide tag.
A CodeIgniter 4 RSS feed is one route, one controller and a short model query. Filter on the status, sort by the publication date, build the XML with XMLWriter so text is escaped, use the post ID for the GUID, make every link and image absolute with site_url and base_url, and send the content type application/rss+xml. Validate it once, and every post you publish can reach your social channels automatically.
No. The framework has no feed component, but PHP includes XMLWriter and CodeIgniter provides routing and a response object, so a feed takes one controller and one route.
Any address works. The names rss.xml and feed are common. Choose one, keep it stable and link to it from your page head so tools can discover it.
The links are built from the base URL in the application configuration or the environment file. Set it to your full public address with https and clear the cache.
Filter on the status field in the model query, so only published rows are returned. Test once with a draft post and confirm that it is missing from the feed.
Yes. Paste the public feed address as a source in PostRSS, connect your social accounts and each new post is shared automatically. Check that the address opens without a login first.
What changed in the networks, what broke, and how to fix it before it costs you reach.
Creadas por Internet Solutions, el equipo detrás de PostRSS. Cada una te ahorra tiempo a su manera.