@bongo/feed makes RSS and Atom feeds for bongo. You write a feed as an output file, and the plugin turns it into RSS 2.0 or Atom 1.0.
Install
bun add -d @bongo/feedUsage
Add the plugin to bongo.config.ts:
import { feed } from '@bongo/feed';
export default {
$site: { url: 'https://example.com/', title: 'My Site', lang: 'en' },
plugins: [feed()],
};A feed is an output file whose frontmatter has format: rss or format: atom. For example, bongo writes content/blog/feed.xml.hbs to /blog/feed.xml:
---
format: rss
title: Blog
description: Latest posts
---
{{#var posts = $section.filter(p => p.params.date).toSorted((a, b) => String(b.params.date).localeCompare(String(a.params.date)))}}
<feed:channel>
{{#each posts as post}}
<feed:item href="{{ post.path }}" title="{{ post.title }}" date="{{ post.params.date }}" summary="{{ post.summary }}" />
{{/each}}
</feed:channel>Then put a <feed:link> in the <head> of your layout:
<feed:link path="/blog/feed.xml" />Each page gets a link to the feed:
<link rel="alternate" type="application/rss+xml" title="Blog" href="/blog/feed.xml"/>How it works
bongo renders the feed file like any other output file, so {{ }} fills in the attributes of each <feed:item>. The plugin then replaces the whole file with RSS 2.0 or Atom 1.0, made from the frontmatter and the <feed:item> elements in <feed:channel>. It drops all text outside <feed:channel>.
Write attribute values with {{ }}, not {{{ }}}. {{ }} escapes &, <, and ", and the plugin decodes each value once before it writes the XML. Thus each character arrives as it is in your content. With {{{ }}}, a value that has a " breaks the element.
<feed:link> reads the frontmatter of the feed at path to get its type and title.
Change format: rss to format: atom, and both the feed and the type of every <feed:link> to it change. You do not edit your layouts or the <feed:channel> block.
The plugin does its work in $hooks.transform.
Elements
<feed:link>
Write it as <feed:link path="…" />. Use it in a layout or a page.
| Attribute | Required | Notes |
|---|---|---|
path |
yes | The URL path of the feed, such as /blog/feed.xml. It must be an output file with format: rss or format: atom. On a site under a subpath, the URL path starts with it, such as /sub/blog/feed.xml, as in $hooks and $page.path. |
title |
no | The link title. Defaults to the title of the feed. |
<feed:channel>
Write it as <feed:channel>…</feed:channel>. It holds the <feed:item> elements and takes no attributes. The frontmatter of the feed file sets the rest:
| Frontmatter | Required | Notes |
|---|---|---|
format |
yes | rss or atom. |
title |
yes | The feed title. |
description |
no | RSS <description>. Atom uses it as <subtitle> when there is no subtitle. |
subtitle |
no | Atom <subtitle>. |
language |
no | Defaults to $site.lang. |
copyright |
no | RSS <copyright>, Atom <rights>. |
link |
no | The page that the feed is for, such as /blog/, as a path or a URL. RSS <link>, Atom <link rel="alternate">. Defaults to $site.url. |
author |
no | Atom <author> of the feed, for entries that do not name their own. Defaults to $site.title. |
stylesheet |
no | A .css or .xsl file, as a path or a URL, that browsers use to show the feed as a page. A relative path resolves under $site.url, and a root-relative path is used as written. |
The URL of the feed comes from its path and $site.url. The latest item date becomes RSS <lastBuildDate> and Atom <updated>. An empty Atom feed gets the build time, because Atom requires <updated>.
Without a stylesheet, a browser shows a feed as a tree of XML. With one, the feed starts with an <?xml-stylesheet?> instruction. Feed readers ignore it.
Prefer CSS to XSLT: browsers are removing XSLT, and Chrome turns it off in November 2026. CSS can style each element of the feed, such as channel > title or item, but it cannot make the <link> of an item clickable.
<feed:item>
Write it as <feed:item … />. Each one becomes an <item> in RSS or an <entry> in Atom, in the same order.
| Attribute | Required | Notes |
|---|---|---|
href |
yes | The URL of the post, such as {{ post.path }}. A relative path resolves under $site.url, and a root-relative path is used as written, as a browser does. |
title |
yes | The post title. |
date |
yes | A date that JavaScript can parse, such as 2025-01-15 or 2025-01-15T10:00:00Z. |
summary |
no | RSS <description>, Atom <summary>. |
content |
no | HTML. RSS <content:encoded>, Atom <content type="html">. |
id |
no | RSS <guid isPermaLink="false">, Atom <id>. Defaults to the URL. |
author |
no | A name. RSS <dc:creator>, because RSS <author> must be an email address. Atom <author><name>. |
tags |
no | Comma-separated. Each one becomes a <category>. |
Errors
The build fails, and the error names the file, when:
- an element has an attribute that it does not support, or misses one that it needs,
- a
<feed:item>has adatethat is not a valid date, - a feed has no
<feed:channel>or notitle, - an output file has a
<feed:channel>but noformat: rssorformat: atom, - the
pathof a<feed:link>is not an output file, or that file has noformat: rssorformat: atom.
License
MIT © Luke Edwards