@sdxc/rss
RSS 2.0 feed builder and parser
- Installs with
- @sdxc/result@sdxc/xml
- Used by
- blog
- Source
- packages/rss
RSS 2.0 feed builder and parser.
It builds feeds for publishing and reads feeds published by other sites, covering the whole
set of standard channel and item fields plus the namespaced extensions feeds lean on in
practice: atom:link, content:encoded, dc:creator, and slash:comments. Anything else
namespaced is preserved rather than discarded, so a feed carrying a custom module survives a
read and a write unchanged.
Installation
npm add @sdxc/rss
RSS.fromXML takes a document from @sdxc/xml,
which installs alongside this package.
Usage
Build A Feed
Describe the channel first, append items, then serialize.
import { RSS } from "@sdxc/rss";
let feed = new RSS({
title: "My Blog",
description: "Articles about web development",
link: "https://example.com",
language: "en-us",
atomLink: {
href: "https://example.com/feed.xml",
rel: "self",
type: "application/rss+xml",
},
});
feed.addItem({
guid: { value: "tag:example.com,2026:post-1", isPermaLink: false },
title: "Hello World",
description: "A short summary",
link: "https://example.com/posts/hello-world",
pubDate: new Date().toUTCString(),
contentEncoded: "<p>Full post content</p>",
});
let xml = feed.toString();
The namespaces the extensions need are declared for you, so the example above writes
xmlns:atom and xmlns:content onto the root element.
Read A Feed
import { RSS } from "@sdxc/rss";
let feed = RSS.parse(xml);
console.log(feed.channel.title);
for (let item of feed.items) console.log(item.title, item.link);
Parsing throws on a document that is not RSS, so wrap the call where a malformed feed is an expected outcome rather than a bug:
try {
return RSS.parse(xml);
} catch (error) {
return recordBadFeed(error);
}
Fetch A Feed
import { RSS } from "@sdxc/rss";
let feed = await RSS.fetch(new URL("https://example.com/feed.xml"));
RSS.fetch asks the origin not to serve a cached copy, requires the response to be ok and
to carry an XML content type, and throws when either fails.
API
new RSS(channel: RSS.Channel)
Creates a feed with the given channel metadata and no items. title, description, and
link are required.
rss.channel
The channel data, as a clone. Assigning a new RSS.Channel replaces it and leaves the items
in place.
rss.channel = { ...rss.channel, language: "en-us" };
rss.items
The items, as clones, in the order they were added.
rss.addItem(item: RSS.Item)
Appends one item. RSS 2.0 requires at least a title or a description.
rss.removeItem(guid: string)
Removes the first item whose guid value matches.
rss.toJSON()
Returns { channel, items } as plain serializable data.
rss.toString()
Serializes the feed to RSS 2.0 XML.
RSS.parse(source: string): RSS
Parses RSS XML. Throws when the text is not XML, or when its root element is not rss.
RSS.fromXML(xml: XML): RSS
Reads a feed out of an already-parsed document, for a caller that parsed the text for some other purpose first.
RSS.fetch(input: URL | RequestInfo, init?: RequestInit): Promise<RSS>
Retrieves a document and parses it.
Supported Fields
Channel
Beyond the required title, description, and link: language, copyright,
managingEditor, webMaster, pubDate, lastBuildDate, category, generator, docs,
cloud, ttl, image, rating, textInput, skipHours, and skipDays.
Item
At least one of title or description, plus link, author, category, comments,
enclosure, guid, pubDate, and source.
Namespaced Extensions
The four common modules, from the RSS Best Practices Profile, have named fields:
| Field | Element |
|---|---|
atomLink | atom:link |
contentEncoded | content:encoded |
dcCreator | dc:creator |
slashComments | slash:comments |
Any other namespaced element round-trips through extensions. Declare its namespace on
channel.namespaces when you write one, since a prefix with no declaration in scope cannot
be serialized.
let feed = new RSS({
title: "Example",
description: "Example",
link: "https://example.com",
namespaces: { media: "http://search.yahoo.com/mrss/" },
extensions: [
{
name: "media:rating",
attributes: { scheme: "urn:simple" },
children: ["adult"],
},
],
});
Notes
Dates are the strings the document holds, so write them in RFC 822 style —
Tue, 14 Apr 2026 09:00:00 GMT, which is whatDate.prototype.toUTCStringproduces.descriptionandcontentEncodedare written as XML text, so embedded HTML is escaped on the way out and arrives unescaped on the way back in.guid,category,enclosure, andatomLinkeach accept a bare string for the common case and an object when attributes matter.Nothing is sanitized. An item's markup is whatever the publisher wrote, and escaping it is the responsibility of whatever renders it.