Publii plugin guide · about eight minutes

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.

Use the implementation patternCopy the test matrix

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.

Correct seam

Register feedXmlOutput for the Atom/XML feed. feedJsonOutput is a separate path.

Correct targets

Change image URLs in media:content url, and HTML img src or srcset inside CDATA.

Preserve

Leave feed links, entry links, IDs, unrelated enclosures, namespaces, and disabled output unchanged.

Small surface, explicit behaviour

A safe modifier pattern

1

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"
}
2

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.

3

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.

4

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
  • feedXmlOutput is registered once
  • Disabled output is byte-for-byte identical
  • media:content url rewrites
  • Every img srcset candidate 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 url remains unchanged
  • XML declaration and namespaces survive
  • The result reparses as XML
Tagged source and real demand

Sources

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