# feed writes RSS 2.0, Atom 1.0 and JSON Feed from one typed model

> feed is an MIT licensed TypeScript library for Node.js that generates RSS 2.0, Atom 1.0 and JSON Feed 1.0 from a single Feed object, with a strongly typed option surface covering enclosures, podcast extensions, WebSub hubs and XSL stylesheets. The whole runtime dependency list is one package, xml-js, and the build is tsdown with vitest and biome.

**jpmonette/feed** — A RSS, Atom and JSON Feed generator for Node.js, making content syndication simple and intuitive! 🚀

- Repository: https://github.com/jpmonette/feed
- Website: https://github.com/jpmonette/feed
- Stars: 1,410 · Forks: 226
- Language: TypeScript
- License: MIT
- Published: 2026-10-16 · Updated: 2026-10-16 · Language: en
- Canonical page: https://hysenlabs.com/projects/jpmonette-feed

## One object, three serialisations

The feed package is described in one line as a RSS 2.0, JSON Feed 1.0 and Atom 1.0 generator for Node.js, with the aim of making content syndication simple and intuitive.

The API is a Feed constructor, an addItem method per post, and a serialiser method per format.

```js
import { Feed } from "feed";

const feed = new Feed({
  title: "Feed Title",
  description: "This is my personal feed!",
  id: "http://example.com/",
  link: "http://example.com/",
  language: "en",
  image: "http://example.com/image.png",
  favicon: "http://example.com/favicon.ico",
  copyright: "All rights reserved 2013, John Doe",
  updated: new Date(2013, 6, 14),
  generator: "awesome",
  feedLinks: {
    json: "https://example.com/json",
    atom: "https://example.com/atom",
  },
  author: {
    name: "John Doe",
    email: "johndoe@example.com",
    link: "https://example.com/johndoe",
  },
});
```

Then the posts go in, and the three outputs come out.

```js
console.log(feed.rss2());
// Output: RSS 2.0

console.log(feed.atom1());
// Output: Atom 1.0

console.log(feed.json1());
// Output: JSON Feed 1.0
```

That shape is the whole value proposition. A site that wants to publish to a dozen feed readers writes one data structure, not three templates, and the format specific details, the channel element versus the feed element versus the top level JSON object, are handled by the library.

The three claims at the top of the README are the ones to check against the implementation. Developer Ready, quickly generate syndication feeds for your website. Strongly typed, developed using TypeScript and type-safe. And Tested, with tests and a snapshot for each syndication format to avoid regressions. The snapshot part is the one that matters most for a generator, because the failure mode of hand written feed XML is that it is valid enough to work until a reader rejects it, and a snapshot per format catches a change in the output that was not intended.

## The option surface covers more than the core three formats

The feed options table is where the library's coverage shows, and several entries are for things that are not in the base format specifications.

The podcast option enables iTunes and Google podcast extensions, with a separate category option for the podcast category name, and the item options carry audio, video and image attachments, each of which is either a string or an Enclosure object with a url, a MIME type, a length in bytes, a title and a duration. The Enclosure interface is exactly what a podcast enclosure needs.

There is a hub option for a WebSub or PubSubHubbub hub URL, which is the mechanism by which a feed reader can be notified that the feed changed rather than polling it. There is a ttl option, the time to live in minutes, which is the RSS equivalent of the same idea.

There is a stylesheet option for an XSL stylesheet URL, which is the trick where the feed itself renders as a styled page in a browser, so a visitor who clicks the feed link sees something readable rather than raw XML. And there is a docs option for the RSS specification documentation URL.

The item options are the interesting complement, and they include several format specific fields that the library has to route correctly. There is id for a unique identifier used by Atom and JSON Feed, guid for the RSS specific GUID, and published for the original publication date as distinct from the date of last modification. The date field is required and is the last modified date.

The extensions option is a list of custom extensions, which is how a site adds a field that no standard format defines without patching the library.

The feedLinks option is the object that makes the multi format story coherent, since it takes the rss, atom and json keys and lets the library advertise the other representations of the same feed, which is what makes the outputs discoverable by a reader that found only one of them.

## The build is tsdown, the tests are vitest, and the linter is biome

The package.json is a good description of a modern small library, and there is nothing in it that is surprising.

```json
"scripts": {
  "build": "tsdown",
  "prepublish": "pnpm build",
  "test": "vitest run",
  "lint": "biome check src",
  "format": "biome format src"
}
```

tsdown is the bundler, with the config living in tsdown.config.ts at the root. Vitest runs the tests, and biome handles both linting and formatting in one tool, with a biome.json at the root, so there is no eslint plus prettier pair to keep in agreement.

The dependency situation is notable. The runtime dependencies list has exactly one entry, xml-js at ^1.6.11, and the dev dependencies are the toolchain, tsdown, typescript at ^5.8.3, vitest at ^4.1.9 and biome.

One dependency is a strong statement about the design. There is no feed parsing library, because this library only generates, and no templating engine, because the output is built as a document structure rather than by interpolating strings into a template. So the entire cost of the library at runtime is a single XML library.

The module format is the other thing worth checking. The package is type module, so it is ESM, and the exports map offers both an import and a require condition pointing at the same lib/feed.js, plus a types entry.

```json
"exports": {
  ".": {
    "import": "./lib/feed.js",
    "require": "./lib/feed.js",
    "types": "./lib/feed.d.ts"
  }
}
```

The engines field requires node >=20 and pnpm >=10, and the packageManager field pins pnpm at 10.10.0 with a hash.

The release history shows the project moving quickly. The tags are 5.2.0 on 2026-01-12, 5.2.1 on 2026-04-19 and 6.0.0 on 2026-07-05, so a major version in the middle of a year, and the last push to main was on 2026-09-12.

## Authors, contributors and categories are first class

The item model treats the people involved in a post as structured data rather than a string, and that is the detail that decides whether the output is useful in a reader.

An item can have an array of authors and an array of contributors, each entry an Author object with a name, an email and a link. The feed itself has a single author.

```typescript
interface Author {
  name?: string;
  email?: string;
  link?: string;
  avatar?: string; // JSON Feed only
}
```

The avatar field is annotated as JSON Feed only, which is a small sign of care about the format boundaries, since JSON Feed has an avatar concept that RSS and Atom do not, and a library that silently emitted it everywhere would produce a document that fails validation in the other two formats.

Items can also have categories, as a list of Category objects, and the feed has addCategory and addContributor methods to add to the feed level. The example calls addCategory with a single string argument, which is a slight mismatch with the Category type in the item options, and that is worth knowing about if you use it, because the two are not the same shape.

The distinction between author and contributor matters for how readers present a post. An author is credited with the work, a contributor is credited with a contribution to it, and the distinction survives into the output. For a site with multiple writers this is the difference between a reader showing a byline and a reader showing a list of names, and getting it wrong is a common failure of hand rolled feeds.

The Enclosure interface carries the media metadata.

```typescript
interface Enclosure {
  url: string;
  type?: string; // MIME type (e.g., "audio/mpeg")
  length?: number; // File size in bytes
  title?: string;
}
```

The length field is the file size in bytes and the duration is the one you would expect for a podcast episode, and the MIME type is what a reader uses to decide whether it can play the item at all.

## Where feed is the wrong choice

The first wrong case is reading feeds. This library generates, and there is no parser here. The dependency list makes that clear, a single XML library and nothing else, and the topics are atom, feed, rss and syndication, with the README describing a generator. If the requirement is to consume feeds, a different package is needed.

The second wrong case is a full syndication service. feed produces a document from data you already have. It does not fetch your content, schedule regeneration, cache the output, diff it against the previous version, or notify subscribers. Those are the parts of running a feed that are actually hard, and a team that needs them needs a platform rather than a serialiser.

The third wrong case is a static site generator that already produces feeds. If your site is already built with a static site generator that emits RSS from its content model, you have a feed generator already, and adding this one means two sources of truth for the same content.

The fourth wrong case is a team that needs a format this library does not implement. RSS 2.0, Atom 1.0 and JSON Feed 1.0 are the three covered. There is no RSS 1.0 output, and no output for the other formats that have appeared over the years. For the three mainstream formats the coverage is complete enough that this is not a limitation, but it is a boundary.

## How it differs from a hand written template

The comparison worth making is with the common approach, which is a template file per format with a loop over the posts, and the difference in approach is where the format knowledge lives.

In a template approach, the format knowledge is in the template, and it is a set of element names and structures that you have to get right for each of the three formats. The templates are also three separate artefacts that can drift, so an item field added for RSS is missing from Atom and nobody notices until a reader complains.

feed inverts that. The format knowledge is in the library, in one place, written by someone who can read the specifications, and the data model above it is format neutral. The consequence is that adding a field to the model and getting it into all three outputs is a change to the library, and the consistency across formats is a property of the implementation rather than something you maintain.

The second difference is the escaping problem. Hand written feed XML means you are interpolating user content into an XML document, and any content containing an ampersand or a less than sign produces invalid XML unless you escape it. Using an XML document library, xml-js in this case, means the escaping is the library's problem. This is the class of bug that produces a feed that works for a week and then breaks on the post with an ampersand in the title, and it is the most common reason feeds break in production.

The third difference is the testing story. The README claims tests and a snapshot for each syndication format to avoid regressions, and that is only possible because the output is produced by code rather than by interpolation. A template approach is much harder to snapshot, since the template itself is part of the input, and it is the format knowledge that would need to be tested rather than the result.

The fourth difference is scope. A template approach is tied to one templating language and one rendering model, and it is often tied to the rest of the site's stack. feed is a plain TypeScript library with one dependency, so it drops into any Node.js project, including a server that has no template engine at all.

## Conclusion

feed fits a Node.js project that publishes a blog or a documentation site and wants RSS, Atom and JSON Feed output from the same data without maintaining three templates or hand writing XML, and it fits a team that wants the output covered by snapshot tests so a format regression is caught in CI. It does not fit a project that needs to parse feeds, since this library only generates them, and it does not fit a team that needs a full syndication platform with caching, scheduling and delivery. Before adopting it, note that the package is ESM first with a CommonJS entry point retained in the exports map, that it requires Node 20 or newer and pnpm 10 or newer, and that the newest release is 6.0.0 from 2026-07-05 with the last push on 2026-09-12.

## FAQ

### How do I install the feed package?

Run pnpm add feed. The package requires node >=20 and pnpm >=10, and it is published as feed with version 6.0.0. The only runtime dependency is xml-js, and the package is ESM first with a CommonJS entry point retained in the exports map.

### Which feed formats does the feed library generate?

RSS 2.0, Atom 1.0 and JSON Feed 1.0, from a single Feed object. You call feed.rss2(), feed.atom1() or feed.json1() to serialise, and the feedLinks option takes rss, atom and json keys so the outputs can advertise each other.

### Does feed support podcast feeds?

Yes. The podcast option enables the iTunes and Google podcast extensions, with a separate category option, and items accept audio, video and image attachments as either a string or an Enclosure object carrying a url, MIME type, byte length, title and duration.

### How is the feed library tested?

The README states that there are tests and a snapshot for each syndication format to avoid regressions, and the package.json runs them with vitest run. Linting and formatting are handled by biome check src, and the build is done with tsdown.

### What is the current version of the feed package and its licence?

The package is MIT licensed with a LICENSE file at the root of the repository, authored by Jean-Philippe Monette. The releases are 5.2.0 on 2026-01-12, 5.2.1 on 2026-04-19 and 6.0.0 on 2026-07-05, and the last push to main was on 2026-09-12.

## Sources

- [jpmonette/feed on GitHub](https://github.com/jpmonette/feed)
- [License: MIT](https://github.com/jpmonette/feed/blob/main/LICENSE)
- [Project website](https://github.com/jpmonette/feed)
- [README](https://github.com/jpmonette/feed/blob/main/README.md)
- [Releases](https://github.com/jpmonette/feed/releases)

---

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