# rss is a small Node library that turns plain objects into feed XML

> The rss package is a MIT licensed Node library for generating RSS feeds, where you construct a feed with a channel object, push items onto it, and call xml() to get a string. It handles escaping for you, supports enclosures, GeoRSS coordinates and custom namespaces for podcast tags, and leaves caching and delivery entirely to the application.

**dylang/node-rss** — RSS feed generator for Node.

- Repository: https://github.com/dylang/node-rss
- Website: https://npmjs.org/package/rss
- Stars: 1,042 · Forks: 128
- Language: JavaScript
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/dylang-node-rss

## The whole API is construct, add, serialise

The rss package is described as an RSS feed generator, with the promise to add RSS feeds to any project, and it supports enclosures and GeoRSS. The API surface is three moves.

You require the module and construct a feed with the channel options:

```js
var RSS = require('rss');

var feed = new RSS(feedOptions);
```

You add items to it, one call per entry, and the feed can have any number of them, with the README noting that most feeds use twenty or fewer:

```js
feed.item(itemOptions);
```

And you serialise it, which returns the XML as a string:

```js
var xml = feed.xml({indent: true});
```

There is no HTTP layer, no template engine, no model binding and no plugin system. The library takes plain JavaScript objects and produces a document, and everything about how that document reaches a reader is the application's problem.

That narrowness is a strength for one use and a limit for another. If you already run a Node service, adding this is a matter of one require and a route handler. If you want a publishing system, a feed validator, Atom output or conditional request handling, you are still writing that part yourself, and the README does not document any of it.

The package version is 1.2.2, the main entry is lib/index, and the only runtime dependencies are mime-types and xml, which is a very small surface for something that produces a document consumed by dozens of reader implementations.

## Feed options are the channel, and two of them are required

The feed options map onto the RSS channel element, and the required ones are the title, the feed_url and the site_url.

Beyond those, the optional channel fields cover the descriptive parts of a feed: description, generator, image_url for a small image feed readers can use, docs pointing at the feed documentation, managingEditor for who manages the content, webMaster for who handles feed availability and technical support, copyright, language and categories as an array of strings.

Three options are worth reading twice because they control behaviour rather than description. pubDate is the publication date for the content in the feed. ttl is the number of minutes the feed may be cached before it should be refreshed from the source, which is a hint to readers and to any intermediary cache. And hub is the PubSubHubbub hub URL, which is how a feed tells the world where to send real time notifications when something changes.

The last two options are the escape hatch. custom_namespaces puts additional namespaces into the rss element, without the xmlns: prefix, and custom_elements puts additional elements into the feed using node-xml syntax. Together they are how a podcast feed carries iTunes metadata without the library needing to know what a podcast is.

One field is easy to get wrong in practice: ttl is documented as an integer, while the example passes the string '60'. The generated document is what readers see, so it is worth checking what actually lands in the channel element rather than trusting the type annotation.

## The guid is the one field you cannot change later

The item options are largely self explanatory, with two rules that matter more than the rest.

The first is the guid. It is a unique string that feed readers use to know whether an item is new or has already been seen, and the instruction is blunt: if you use a guid, never change it. If you do not provide a guid, then your item URLs must be unique instead. In practice that means the guid is the identity of the item for every reader who ever subscribed, and reusing a guid for different content makes an entry disappear from a reader that has already seen it, while changing a guid makes an entry reappear as new.

The second is the date. It is the date and time of when the item was created, and it is required. Feed readers use it to determine the sort order, and some readers will also use it to decide whether content should be presented as unread. That makes it a user visible behaviour rather than metadata.

The rest of the item contract is straightforward. title is required, description is required and may contain HTML, though link and image URLs in it must be absolute paths including the hostname. url points at the item. categories is an optional array of strings. author is optional and falls back to the feed author, which is typical except on multi-author blogs, and that fallback is why a per item author is worth setting where several people write.

The date accepts a Date object or a date string, and the README's example uses a plain string, with the note that it can be any format that js Date can parse. That flexibility is convenient and also a source of surprises, since parsing depends on the runtime's date parser rather than on the library.

## Enclosures and coordinates are first class fields

Two optional item features go beyond the basic RSS item, and both are handled by the library rather than left to custom elements.

Enclosure is an object with a required url and a required file, where the file is a path to a binary file or a URL, and an optional size and an optional type. If the type is not provided, the mimetype is guessed based on the extension of the file or URL, and passing type overrides the guess. The example shows the shape:

```js
{
  'url'  : 'http://www.example.com/path/to/image',
  'size' : 1668,
  'type' : 'image/jpeg'
}
```

The distinction between url and file matters when you read the documentation literally: the url is what the reader downloads, and the file is where the size and the mimetype come from. Supplying the size is what makes the reader show a progress bar, and the mime type guessing is what saves you from hardcoding image/jpeg everywhere.

Coordinates are the other one. lat and long are optional numbers, and the README describes them as the latitude and longitude coordinate of the item for GeoRSS. That is a real distinction from a plain RSS feed: a feed with coordinates is a GeoRSS feed, and the item becomes a point on a map in readers that support it, which is a meaningful feature for a site publishing places, events or anything with a place attached to it.

Both features are opt in per item, so a feed that mixes posts and events can carry coordinates on the events and leave them off the posts.

## Custom namespaces are how podcast feeds get their tags

The extension mechanism is the part of this library that is more capable than it first appears, and the example in the README is a podcast feed.

The feed declares the namespace in custom_namespaces, with the namespace url and the prefix as the key, and without the xmlns: prefix:

```js
custom_namespaces: {
  'itunes': 'http://www.itunes.com/dtds/podcast-1.0.dtd'
}
```

Then custom_elements adds the elements themselves, using node-xml syntax, which is how attributes are expressed. The example sets itunes:subtitle and itunes:author as plain values, itunes:owner as a nested object with itunes:name and itunes:email, itunes:image with an _attr object holding an href, and itunes:category with nested categories each carrying a text attribute:

```js
custom_elements: [
  {'itunes:subtitle': 'A show about everything'},
  {'itunes:image': {
    _attr: {
      href: 'http://example.com/podcasts/everything/AllAboutEverything.jpg'
    }
  }},
  {'itunes:category': [
    {_attr: { text: 'Technology' }},
    {'itunes:category': { _attr: { text: 'Gadgets' }}}
  ]}
]
```

The same mechanism works at the item level, where the example adds an itunes:duration of 7:04 alongside an itunes:author and an itunes:image.

Two details make this usable rather than merely possible. The nesting expresses the XML tree directly, so a tag with attributes and children is one object, and the underscore prefixed _attr key keeps attribute syntax distinct from a child element, which is the same convention the xml package uses. The result is that a feed reader sees iTunes metadata, while your code stays ordinary JavaScript objects.

## Escaping is handled, caching is your problem

The Notes section at the end of the README is short and both halves matter.

The first note is that you do not need to escape anything, because the module escapes characters when necessary. That is a real convenience, since the alternative is sprinkling entity encoding through template code and getting it subtly wrong for descriptions containing ampersands or angle brackets.

The second note is about performance and it is a warning as much as a tip: the module is very fast, but you might as well cache the output of xml() and serve it until something changes. The example in the README does exactly that, with a comment to cache the XML to send to clients.

That is sound advice, and it is also the architecture the library implies. There is no internal cache and no incremental update, so the natural implementation is a feed object you rebuild or refresh when your content changes, and a cached XML string that your HTTP layer serves with whatever cache headers your deployment needs. A request time library that generated the document on every request would work but would waste the thing the library is good at.

The output formatting is a small but pleasant detail. The xml method takes an indent option, which can be a boolean or a string. Passing true uses four spaces, and you can pass a string such as a tab character or two spaces to choose your own, with the default being no tabs, that is, compressed output.

## An old toolchain in a small package

The repository layout is that of a project from a few years ago, and it is worth knowing before you adopt it.

There is a lib/ directory with the implementation, a templates/ directory, a test/ directory, a Gruntfile.js for the build and lint tasks, a .jshintrc for the linter, a .npmignore and an .editorconfig. The linting runs through grunt with grunt-contrib-jshint, and the tests run on tape with tap-difflet to produce a readable diff, with the test script piping tape output through tap-difflet. There is also a testling configuration listing a browser matrix, and a test:browser script that runs the same tests across IE 9 and later, Chrome, Firefox, Safari, Opera, iPhone, iPad and the Android browser.

The browser matrix is a detail worth pausing on. A feed generator that is tested in browsers suggests the package was written to work outside a server context too, which is a real use case for a small library, and it is also a sign of the package's age.

Maintenance comes from a different direction. There is a renovate.json at the top level, which is the configuration for automated dependency updates, and the runtime dependencies are pinned to specific versions, mime-types 2.1.35 and xml 1.0.1. The package is MIT licensed, the repository is not archived, and the last push was on 2026-10-14. There are no GitHub releases, so the version on npm at 1.2.2 is the version to look at.

The dependencies are the healthy part of this story. Two small packages with no transitive weight is about as low a risk as a dependency can be, and the only thing to check is that xml 1.0.1 and mime-types 2.1.35 are still the versions you want in your tree.

## Conclusion

rss suits a Node service that needs to publish a feed next to something else it already serves, a blog engine, or a podcast setup that needs iTunes tags, and it costs nothing to add because it has one runtime dependency pair and no framework around it. It does not suit a project that needs Atom output, multiple feed formats, HTTP caching headers or a feed validator, since the README documents RSS only and stops at producing a string. Verify first that the fields your readers depend on are covered, that your guid values are stable, and that you cache the xml() output rather than regenerating it per request, which is the advice the README itself gives.

## FAQ

### How do I create an RSS feed with the rss package?

Require the module, construct a feed with the channel options, add items with feed.item(itemOptions), then call feed.xml() to get the XML as a string. The required feed options are title, feed_url and site_url.

### Why does the rss package insist on a stable guid?

The guid is the unique string feed readers use to know whether an item is new or has already been seen, and the README says that if you use a guid you should never change it. If you do not provide one, your item URLs must be unique instead.

### Can the rss package produce podcast and iTunes tags?

Yes, through custom_namespaces to declare the namespace in the rss element and custom_elements to add the elements using node-xml syntax, with the _attr key for attributes. The README's example adds itunes subtitle, author, owner, image and category tags.

### Does the rss package escape XML characters for me?

Yes. The README notes that you do not need to escape anything, because the module escapes characters when necessary. Item descriptions may contain HTML, though link and image URLs inside them must be absolute paths including the hostname.

### Should I cache the generated feed XML?

The README says the module is very fast but you might as well cache the output of xml() and serve it until something changes, and its own example caches the XML string for sending to clients.

## Sources

- [dylang/node-rss on GitHub](https://github.com/dylang/node-rss)
- [Issues](https://github.com/dylang/node-rss/issues)
- [License: MIT](https://github.com/dylang/node-rss/blob/master/LICENSE)
- [Project website](https://npmjs.org/package/rss)
- [README](https://github.com/dylang/node-rss/blob/master/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/dylang-node-rss
