Rewrite feed images. Keep the feed's identity intact.
Publii exposes the rendered XML feed to plugins before it writes feed.xml. A focused feedXmlOutput modifier can move image delivery to a CDN, but a global domain replacement can also rewrite the feed's self link, post links, and IDs. Target image-bearing attributes and test the XML as a document.
Publii's interface calls this an RSS feed; the current default XML template is Atom. This independent guide is pinned to Publii v.0.47.9-build-17481. Custom feed templates and namespaces may require separate handling.
Register feedXmlOutput for the Atom/XML feed. feedJsonOutput is a separate path.
Change image URLs in media:content url, and HTML img src or srcset inside CDATA.
Leave feed links, entry links, IDs, unrelated enclosures, namespaces, and disabled output unchanged.
A safe modifier pattern
Make the behaviour opt-in
Add a checkbox whose manifest default is the boolean false, not the string "false". Publii supplies manifest values to plugin configuration; a string is truthy in JavaScript and can silently turn an intended default-off feature on.
{
"name": "cdnFeedImages",
"label": "Use CDN for feed images",
"value": false,
"type": "checkbox"
}Register the XML modifier
Register feedXmlOutput alongside any existing HTML modifier. Publii renders the feed, runs the registered XML modifiers sequentially, passes each modifier's result to the next, and only then saves feed.xml.
addModifiers() {
this.API.addModifier(
'feedXmlOutput',
this.modifyFeedXmlOutput.bind(this),
999,
this
);
}
modifyFeedXmlOutput(renderer, xmlCode) {
if (!this.config.cdnFeedImages) return xmlCode;
return rewriteFeedImageUrls(xmlCode, renderer.siteConfig.domain, this.config.url);
}Return the original string directly while disabled. That makes byte-for-byte preservation both possible and easy to prove.
Rewrite attributes, not every occurrence of the domain
Do not run a global siteDomain → cdnDomain replacement over the XML. Publii's default template uses the site domain in the feed self link and feed ID, while each post URL appears in both its entry link and ID. Those are identity and navigation values, not image delivery URLs.
Limit the transformation to url on media:content, plus src and every candidate in srcset on img. Confirm the old URL belongs to the exact origin before replacing it; a similarly named host such as assets.example.com must not match example.com accidentally.
Respect XML outside and HTML inside
The document is Atom XML, but its summaries and content can contain HTML inside CDATA. Parsing the entire response as HTML and serialising it back can change the XML declaration, namespaces, casing, CDATA, or envelope. Use an XML-aware transform, or narrowly handle the intended tag attributes while preserving every other byte.
Likewise, do not rewrite every XML attribute named url. An unrelated enclosure url may describe audio or another resource with different delivery rules.
Behaviour checks
- Manifest default is the boolean
false feedXmlOutputis registered once- Disabled output is byte-for-byte identical
media:content urlrewrites- Every
img srcsetcandidate rewrites - Single- and double-quoted attributes work
Preservation checks
- Feed self link and feed ID remain unchanged
- Entry link and entry ID remain unchanged
- Similarly named domains remain unchanged
- An unrelated
enclosure urlremains unchanged - XML declaration and namespaces survive
- The result reparses as XML
Sources
- Publii renderer at v.0.47.9-build-17481 — renders XML and JSON feeds through separate modifier names before saving them.
- Publii modifier runner at the same tag — passes each modifier's output to the next registered modifier.
- Publii's tagged default Atom template — shows self and entry links, IDs,
media:content, and HTML images inside CDATA. - publii-cdn issue #2 — the public request for image-CDN rewriting in the generated feed.
- publii-cdn PR #3 — a tested implementation of this bounded pattern. It is pending maintainer review, not merged, adopted, commissioned, or customer work.
Need this kind of change in an existing plugin?
The fixed £75 feature scope covers one bounded configuration, modifier, event, or output behavior in one existing plugin and one supported Publii version. A separate £45 scope covers one reproducible defect. Both include focused evidence and install or rollback notes; payment is due only after the written acceptance checks pass.
Check the £45 / £75 scopesDescribe the public change