RSS Feed Block
Renders entries from an external RSS feed, reusing the listing machinery. Each entry is rendered with a configurable item type (variation).
Live example
6.0.9
The Plone Content Management System
6.0.8
The Plone Content Management System
5.2.14
The Plone Content Management System
6.0.7
The Plone Content Management System
5.2.13
The Plone Content Management System
6.0.6
The Plone Content Management System
Developer Reference
Schema
Pass this object inside the blocks option when calling initBridge() to register this block type with the admin UI. See Custom Blocks for the full setup guide.
{
"rssFeed": {
"id": "rssFeed",
"title": "RSS Feed",
"blockSchema": {
"fieldsets": [
{
"id": "default",
"title": "Default",
"fields": [
"feedUrl",
"count",
"variation",
"fieldMapping"
]
}
],
"properties": {
"feedUrl": {
"title": "Feed URL",
"widget": "url"
},
"count": {
"title": "Max items",
"type": "number",
"default": 6
},
"variation": {
"title": "Item Type",
"widget": "blockTypeSelect",
"filterConvertibleFrom": "@default",
"default": "summary"
}
}
},
"schemaEnhancer": {
"inheritSchemaFrom": {
"typeField": "variation",
"mappingField": "fieldMapping",
"defaultsField": "itemDefaults"
}
}
}
}JSON Block Data
Example JSON as stored in the Plone content API. This is the data structure your component will receive in the block prop.
{
"@type": "rssFeed",
"feedUrl": "https://pypi.org/rss/project/plone/releases.xml",
"count": 6,
"variation": "summary"
}Rendering
How this block renders in your frontend. Add its handling to your renderer, or — for list-style blocks — register a fetcher and reuse your list rendering.
This block has no bespoke renderer — register the fetcher below, then render its items exactly like any list block. See the Listing example for the render component in React, Vue and Svelte, Listings for how expansion works, and Custom Blocks for registering a block type. Only the fetcher below is block-specific.
// packages/helpers — client-side, best-effort (CORS-permitting feeds).
export function rssFetcher() {
return async function fetchItems(block, { start, size }) {
let entries = [];
try {
const res = await fetch(block.feedUrl);
entries = parseRssEntries(await res.text()); // → [{ '@id': link, title, description, pubDate }]
} catch {
return { items: [], total: 0 }; // CORS / parse failure → empty feed, never throws
}
if (block.count != null) entries = entries.slice(0, block.count);
return { items: entries.slice(start, start + size), total: entries.length };
};
}
// Dependency-free parse so it runs in the browser AND node (no DOMParser):
function parseRssEntries(xml) {
const out = [];
const tag = (b, n) => (new RegExp(`<${n}\\b[^>]*>([\\s\\S]*?)<\\/${n}>`, 'i').exec(b) || [])[1];
for (const m of xml.matchAll(/<item\b[^>]*>([\s\S]*?)<\/item>/gi)) {
const b = m[1];
out.push({ '@id': tag(b, 'link') || '', title: tag(b, 'title') || '', description: tag(b, 'description') || '', pubDate: tag(b, 'pubDate') });
}
return out;
}// 1. Register the fetcher when you init the bridge (keyed by block @type)
initBridge(origin, { blocks, fetchItems: { rssFeed: rssFetcher() } });
// 2. Render — identical to any list block
const { items } = await expandListingBlocks([blockId], {
blocks: { [blockId]: block },
fetchItems: { rssFeed: rssFetcher() },
itemTypeField: 'variation',
});
items.forEach((item) => renderBlock(item)); // your normal per-block renderer