Library / SDK
danmactough/node-feedparser avatar
danmactough/node-feedparser

Feedparser: a streaming RSS, Atom, and RDF parser for Node.js

Robust RSS, Atom, and RDF feed parsing in Node.js

1,972 stars182 forksJavaScriptNOASSERTION

At a glance

What is it?
The feedparser package parses syndication feeds with sax and hands back a stream of normalised article objects, with relative URL resolution and real XML namespace handling as its distinguishing features. It is a transform stream, so a feed is consumed as it arrives rather than buffered whole.
Who is it for?
Adopt Feedparser if you consume feeds from sources you do not control, since its relative URL resolution and namespace handling cover the cases that break simpler parsers. Do not adopt it if you need a modern async-iterator-only API, because the stream-first design carries an older dependency stack.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 7, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • danmactough/node-feedparser1,972 ★ · -2
Star history
Repository2026-10-162026-11-07
danmactough/node-feedparser2k2k

-2 Stars in the last 22 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Two features most parsers do not have

The README names the two capabilities that distinguish this parser, and they are both about feeds that are technically valid but awkward. The first is that it resolves relative URLs, with the example given being the kind seen in Tim Bray's ongoing feed. The second is that it properly handles XML namespaces, including feeds that define a non-default namespace for the main feed elements.

Those two problems are the ones that produce a parser that works on the blog you test against and fails on plenty of feeds in production. A relative link in a feed means the parser has to know the feed's own URL in order to construct an absolute one, and a namespace declared on a non-default prefix means a naive element lookup silently finds nothing. Feedparser was written to handle both.

The package is called feedparser, it is on npm, and the version in the package manifest is 2.6.0, with the description saying it does its work using sax js. The author is Dan MacTough. The package manifest declares an MIT licence, though the repository metadata reports a custom licence that GitHub cannot classify, so the LICENSE file is what governs. The last push to the repository was on 2026-05-18, and there are no GitHub releases, so the npm version is the reference point rather than a release tag.

The dependency list tells you the vintage. It uses readable-stream 2.x and sax pinned to a range below 1.4.4, with several lodash single-function packages, and the engines field asks for Node 10.18.1 or newer.

A transform stream, not a promise that resolves to an array

The API section defines Feedparser as a transform stream operating in object mode, with XML in and JavaScript objects out. Each readable chunk is an object representing an article in the feed. That single design decision explains most of the API, including the unusual event names and the way errors are handled.

Because it is a stream rather than a buffer-then-parse function, a large feed does not have to be held in memory in full, and the first articles arrive while the rest of the document is still being read. The events are meta, called with the feed meta when it has been parsed, and error, called whenever there is a fatal parser error. Articles arrive through the data event, or through read() after a readable event.

The installation is one command:

bash
npm install feedparser

The package also ships a command line executable, wired in the manifest as a bin entry pointing at bin/feedparser.js, and it includes TypeScript declarations through the types field pointing at index.d.ts, with a tsconfig.json and tsconfig.src.json in the repository for the typed build.

Reading a feed with events

The basic example in the README shows the shape of the event-driven consumption. You create a FeedParser instance, pipe a fetched response body into it, and read articles in the readable handler:

js
var feedparser = new FeedParser([options]);

req.then(function (res) {
  if (res.status !== 200) {
    throw new Error('Bad status code');
  } else {
    res.body.pipe(feedparser);
  }
}, function (err) {
  // handle any request errors
});

feedparser.on('readable', function () {
  var stream = this;
  var meta = this.meta;
  var item;
  while (item = stream.read()) {
    console.log(item);
  }
});

Two details in that snippet matter. The meta property is read from the feedparser instance inside the readable handler, and the documentation notes that the meta is always available in the context of the feedparser instance, so you do not have to wait for the meta event to start reading articles. And the read loop is the standard drain pattern, reading until the chunk is null, so you process every queued article before returning to the event loop.

The error handler is attached to the feedparser itself, separate from the promise rejection handler for the fetch. That separation reflects the two independent failure modes, since the network request can fail and the feed can be malformed, and a parser error arriving on the stream after the response succeeded is not caught by the promise's rejection callback.

Async iteration and the stream error ordering problem

The package can also be consumed with async iteration, and the README spends most of that passage on a subtlety worth understanding. When using async iteration, prefer stream.pipeline, or a promisified version of it, so stream errors are handled before data starts flowing. Async iterator usage with pipeline requires Node v12 or newer. If you use pipe, or otherwise start writing to the FeedParser before iteration begins, you have to attach an error handler on feedparser yourself.

js
var pipeline = require('stream/promises').pipeline;

async function main() {
  var res = await fetch('http://someurl.site/rss.xml');
  if (res.status !== 200) throw new Error('Bad status code');
  var feedparser = new FeedParser(options);
  try {
    await pipeline(res.body, feedparser,
      async function (feedparserIterable) {
        for await (var item of feedparserIterable) {
          console.log(item.title);
        }
      })
  } catch (err) {
    console.error(err);
  }
}

The reason for the recommendation is ordering. If you pipe the response into the parser and then start iterating, the stream can begin emitting and even fail before your error handler is attached, and the failure is lost rather than thrown. pipeline sets up the error propagation before the first chunk moves, which is the difference between catching a malformed feed and seeing an unhandled error event. The comment in the README also notes that stream/promises requires Node v15 or newer, and that the same behaviour can be had by promisifying require('stream').pipeline instead.

This is old advice that is still correct, and it is the single most common way people get a feed parser to fail mysteriously on a malformed feed.

The options that change what you get back

Five options control the observable behaviour, and each overrides a default that is usually what you want.

The normalize option, set to false, turns off the default behaviour of parsing feeds into an object containing generic properties patterned after, although not identical to, the RSS 2.0 format regardless of the feed's own format, and of resolving all relative URLs including those embedded in HTML content fields. The addmeta option, set to false, stops the feed's meta information being added to each article. The guidlink option, set to false, stops an RSS item's guid being used as the item link when the item has no link and the guid starts with http: or https:.

The feedurl option is the one worth reading twice. FeedParser is very good at resolving relative URLs, including those embedded in HTML content fields, but some feeds use relative URLs without declaring the xml:base attribute anywhere in the feed. That is valid, but the parser cannot know the feed's URL before it starts parsing, so if it discovers the URL later it goes back and resolves the relative URLs it has already seen, which takes a little time. If you want to be sure every relative URL is resolved, set feedurl to the feed's URL yourself.

The last one is resume_saxerror, set to false, which makes a SAXError abort parsing and be emitted on the error event. The default is to silently handle SAXErrors and resume, and the author's reasoning is that in his experience SAXErrors are not usually fatal, so this is usually helpful behaviour. When it is left at the default, the errors are collected in feedparser.errors instead of being emitted.

The uniform shape, and the original data underneath

The output model deserves a careful read, because it is the reason to choose this parser over a format-specific one. A parsed feed is split into a meta portion, emitted on the meta event, and one or more articles, emitted on the data event or read after a readable event.

Regardless of the format, the meta and each article contain a uniform set of generic properties patterned after, although not identical to, the RSS 2.0 format, as well as all of the properties originally contained in the feed. The README gives a concrete example: an Atom feed may have a meta.description property, but it will also have a meta['atom:subtitle'] property. The generic properties give you a format-independent interface, and the original namespaced properties stay available for when you need the specific value.

That is the whole point of the normalisation layer, since you do not have to know whether you are looking at RSS or Atom, and you do not have to handle the differences between them. When you do need the original, it is there under its own name.

One caveat is called out in the options section rather than the output section. The meta object may be enriched until the stream ends, because while it is bad practice and borderline pathological, feeds can legally include additional channel metadata after articles. Feed metadata is available as soon as the parser has enough information to emit the first article, but if you need complete metadata you have to handle the meta event and keep the emitted object until the stream ends. If you only need the metadata available when each article streams, item.meta works as usual.

Against XML parsing by hand, and against a DOM-based reader

The realistic alternative is to take the feed as a string and parse it with a general XML library, then walk the tree yourself. The difference in approach is that Feedparser encodes the quirks of syndication formats in one place, and you get a stream of normalised objects. Doing it by hand means handling RSS 2.0, RSS 1.0 as RDF, and Atom separately, since each has a different vocabulary for the same concepts, and you end up writing the normalisation layer yourself.

The second alternative is a DOM-based or XPath-based reader. Those tools are excellent at arbitrary XML and terrible at the specific problem of feeds, because they will not resolve relative URLs against the feed location, they will not know that a link element can be absent while a guid can stand in for it, and they will happily hand you namespaced element names that differ between two feeds that are otherwise equivalent.

The cost of Feedparser is that it is a large surface with opinions baked in, and the opinions are the ones from 2010s era Node. The dependency stack reflects that, with readable-stream 2.x, sax rather than a modern parser, and four separate lodash packages for individual functions. The engine field asks for Node 10.18.1, and the typecheck script is written to skip itself on pre-Gallium releases, which is a small sign of how far back the support matrix reaches. The maintainer points to a working implementation at the feedRead project as a reference for handling the awkward parts, and the examples directory holds a simple and a complete example, with the README asking you to review the complete one as a starting point for a real application.

Editorial conclusion

Adopt Feedparser if you consume feeds from sources you do not control, since its relative URL resolution and namespace handling cover the cases that break simpler parsers. Do not adopt it if you need a modern async-iterator-only API, because the stream-first design carries an older dependency stack. Verify first that the feed URL is passed as the feedurl option when a feed uses relative links without declaring xml:base, since the parser cannot resolve those reliably without it.

Frequently asked questions

How do I install and use Feedparser in Node.js?

Run npm install feedparser, then create a FeedParser instance and pipe a fetched response body into it. Attach a readable handler and call read() in a loop to get article objects, and attach an error handler for fatal parser errors. The package also ships a feedparser command line executable and TypeScript declarations.

Why should I use stream.pipeline with Feedparser async iteration?

The README recommends pipeline, or a promisified version, so stream errors are handled before data starts flowing. If you pipe into the parser and then begin iterating, an error can arrive before your handler is attached. Async iteration with pipeline requires Node v12 or newer, and stream/promises requires Node v15 or newer.

What does the feedurl option do in Feedparser?

It tells the parser the URL of the feed so relative URLs can be resolved reliably. Some feeds use relative URLs without declaring the xml:base attribute, and the parser can only go back and resolve URLs it has already seen if it discovers the feed URL later, which takes a little time.

What happens to SAX errors in Feedparser?

By default they are handled silently and parsing resumes automatically, because in the author's experience SAXErrors are not usually fatal, and they are collected in feedparser.errors. Setting resume_saxerror to false makes the SAXError be emitted on the error event and aborts parsing.

Does Feedparser give me the same object shape for RSS and Atom feeds?

Yes. The meta and each article contain generic properties patterned after the RSS 2.0 format regardless of the feed format, plus all the original properties. An Atom feed, for example, gets a meta.description and also a meta['atom:subtitle'].

Official sources

  1. danmactough/node-feedparser on GitHub
  2. Issues
  3. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/danmactough-node-feedparser.svg)](https://hysenlabs.com/projects/danmactough-node-feedparser)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/danmactough-node-feedparser.svg)](https://hysenlabs.com/projects/danmactough-node-feedparser)

Was this useful?