
Short answer: To serve an RSS feed from a CakePHP application, connect a route for /feed.xml to a controller action, read the newest published articles with the ORM, render a small PHP template that escapes every value with the h() helper, format dates in UTC and return the response with the type rss. Add an ETag and a short cache header so repeat requests get a cheap 304 answer, test the address with curl and a validator, and then give the public URL to an auto-poster so every new article reaches your social channels.
CakePHP is one of the longest-lived PHP frameworks, and many business sites, intranets and customer portals still run on it. Sooner or later such a site publishes something that people outside it want to follow: news, product updates, events or listings. A feed is the standard way to let readers, aggregators and auto-posters watch that content without an account or an API key. This guide builds a complete CakePHP RSS feed with caching and conditional requests, explains each decision and ends with the checks that keep the feed dependable once other systems rely on it.
A feed is worth adding whenever the application publishes content that people want to follow:
Without a feed, every announcement has to be copied to social networks by hand. If your content lives in a blog or a CMS instead, use the feed it already provides; see how to find your WordPress feed link. For other PHP frameworks, see the CodeIgniter guide.
Add one route that maps the public address to a controller action. The route belongs in config/routes.php, inside the scope that handles public pages. Using a fixed address such as /feed.xml means that you can keep it unchanged for years, even if you rebuild the rest of the site.
$routes->scope('/', function (RouteBuilder $builder): void {
$builder->connect('/feed.xml', ['controller' => 'Articles', 'action' => 'feed']);
});Keep the public address of the site in configuration rather than reading it from the incoming request. Behind a proxy, the request may show an internal host name, and a feed full of internal addresses is useless to readers. A common choice is a key in config/app_local.php that you read with Configure::read.
The action reads the newest published articles, builds an ETag from them, answers conditional requests and only then renders the template. The condition on the publication time matters. Without it, an article scheduled for tomorrow would appear in the feed today, and an auto-poster would share it before it exists on the website.
use Cake\Core\Configure;
use Cake\I18n\DateTime;
public function feed()
{
$articles = $this->fetchTable('Articles')->find()
->select(['id', 'title', 'slug', 'summary', 'published_at'])
->where(['status' => 'published', 'published_at <=' => DateTime::now()])
->orderByDesc('published_at')
->limit(20)
->all();
$seed = '';
foreach ($articles as $article) {
$seed .= $article->id . ':' . $article->published_at->getTimestamp() . ';';
}
$response = $this->response
->withType('rss')
->withEtag(md5($seed))
->withHeader('Cache-Control', 'public, max-age=300');
if ($response->checkNotModified($this->request)) {
return $response;
}
$this->setResponse($response);
$this->viewBuilder()->disableAutoLayout()->setTemplate('feed');
$this->set([
'articles' => $articles,
'siteUrl' => rtrim(Configure::read('App.siteUrl'), '/'),
]);
}The example uses the CakePHP 5 method orderByDesc; in CakePHP 4 use order with a descending direction instead. The method checkNotModified compares the ETag with the If-None-Match header of the request and, when they match, turns the response into a 304 without a body. The ETag is built from the identifiers and publication times of the items, so it changes whenever a new article appears or an old one is replaced.
Create templates/Articles/feed.php. The template contains only the feed. Every value goes through h(), which escapes the characters that are special in XML. Print the XML declaration from PHP, because a literal question mark and bracket at the start of a PHP file can be mistaken for a PHP tag.
<?= '<?xml version="1.0" encoding="UTF-8"?>' ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title><?= h('Example News') ?></title>
<link><?= h($siteUrl) ?>/</link>
<description><?= h('The latest articles from Example News') ?></description>
<language>en</language>
<atom:link href="<?= h($siteUrl) ?>/feed.xml" rel="self" type="application/rss+xml"/>
<?php foreach ($articles as $article): ?>
<item>
<title><?= h($article->title) ?></title>
<link><?= h($siteUrl . '/' . $article->slug . '/') ?></link>
<guid isPermaLink="false">article-<?= (int)$article->id ?></guid>
<pubDate><?= gmdate(DATE_RSS, $article->published_at->getTimestamp()) ?></pubDate>
<description><?= h($article->summary) ?></description>
</item>
<?php endforeach; ?>
</channel>
</rss>Open /feed.xml in a browser or with curl. You should see well-formed XML with at most twenty items, newest first.
| Feed field | Source in the example | Rule to keep |
|---|---|---|
| title | The title column | Plain text, escaped, no markup |
| link | Site address plus slug | Absolute address that returns 200 directly |
| guid | The row id with a prefix | Never changes, even if the slug changes |
| pubDate | The publication time in UTC | English day and month names, never in the future |
| description | The summary column | One or two sentences that make sense alone |
The identifier is the most important decision. An auto-poster remembers which items it has already shared by their GUID. If the value changes when someone edits a slug or republishes an article, the same story is shared again. Using the database id with a fixed prefix avoids that. The problem and its fixes are described in why a feed auto-posts the same article twice.
RSS dates follow RFC 822, with English abbreviations for days and months. The PHP function gmdate with the DATE_RSS constant always prints English names and a zero offset, whatever the language of the server or of the CakePHP application. Do not use the translated date formatting of the framework for feeds; a site set to another language would print month names that readers cannot parse. If you see date warnings in a validator, read the guide to RSS date format errors.
Auto-posters, readers and search engines request a feed often, and almost always nothing has changed. The action above handles that in two ways. It sends an ETag, and when the client returns the same value, the answer is a 304 with no body and no template rendering. It also sends a Cache-Control header that lets a proxy or a content delivery network keep the response for a few minutes. If the query is heavy, store the result with the CakePHP cache engine and clear it when an article is saved. The background to both techniques is in the guide to ETags and conditional GET un the guide to feed caching. Do not cache for hours, because a long cache delays every new post by the same amount.
Two CakePHP features can quietly damage a feed. The first is authentication: if your application requires a login for most actions, allow the feed action explicitly for visitors who are not signed in, otherwise an auto-poster receives a redirect to the login page instead of XML. The second is the debug toolbar, which appends HTML to responses when debug mode is on. Always test the feed with debug switched off, as on the live server, and make sure that nothing prints text before the XML declaration. A single blank line before the declaration is enough to make some validators reject the feed. More causes of broken feeds are collected in what to check when a feed stops updating.
An auto-poster is more useful when each item carries a picture. Add an enclosure element with the address, the byte length and the type of the image, or a media content element if you prefer that namespace. Use an absolute https address and a file of reasonable size. Without an image, many networks show a plain text card. How tools read these elements is explained in the guide to enclosure tags. If the summary contains HTML, wrap it in a CDATA block or escape it, as described in the CDATA guide.
There is a longer checklist in the feed validation guide and a short curl routine in the guide to debugging a feed with curl.
PostRSS has been publishing RSS and Atom feeds to social networks since 2014. Paste the address of your feed 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 item, with its image and link, to the channels you choose among 66 supported networks, messengers, team chats and blogs, including LinkedIn, X, Bluesky, Mastodon and Telegram. Keyword filters, UTM parameters and posting windows let you tune each channel, 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.
CakePHP needs only a route, a short controller action and one template to serve a dependable RSS feed: select published items dated in the past, escape every value with h(), use a stable GUID, format dates in English UTC, build links from a configured public address and return the XML with the right type, an ETag and a short cache. Make sure the feed is reachable without a login, test it with curl and a validator, keep the address unchanged and connect it to an auto-poster. After that, every article you publish can reach your social channels automatically.
Recent versions do not ship one in the core, so the simplest approach is a plain template that prints the XML and escapes each value with h(). The result is easy to read and to test.
Call withType with the value rss on the response. CakePHP maps it to the RSS media type and adds the character set, so browsers and validators recognise the feed.
The authentication component or middleware is protecting every action by default. Allow the feed action for visitors who are not signed in, and test it in a private browser window or with curl.
Between ten and twenty of the newest published items is enough. An auto-poster only needs recent items, and a smaller file is faster to download and parse.
Give every item a stable identifier and use it as the GUID. Avoid editing the publication date of items that are already public, because some tools treat a changed date as a new item.
Yes. PostRSS reads any valid RSS or Atom address that is reachable without a login. Paste the address, connect your channels and new items are shared as soon as they appear.
What changed in the networks, what broke, and how to fix it before it costs you reach.
Izstrādājusi Internet Solutions — PostRSS komanda. Katrs produkts ietaupa laiku savā veidā.