npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bliztek/feed-generator

v2.2.0

Published

A simple and lightweight Node.js library for generating RSS 2.0, Atom, and JSON Feed formats.

Readme

Feed Generator: RSS, Atom, JSON

Build Status Codecov npm License Downloads Bundle Size

A simple and lightweight Node.js library for generating RSS 2.0, Atom, and JSON Feed formats. Define your feed once, output to any format. Zero dependencies. Podcast-ready with full iTunes namespace support.


Features

  • One unified API -- define your feed once, generate RSS, Atom, or JSON Feed output.
  • Fluent builder -- construct feeds incrementally with a chainable FeedBuilder API.
  • Syndication-ready -- RSS content:encoded and media:content for full-content syndication to platforms like Dev.to.
  • Custom extensions -- inject arbitrary XML namespaces, elements, and JSON Feed properties (Dublin Core, GeoRSS, etc.).
  • Podcast support -- full iTunes namespace for Apple Podcasts, Spotify, and other directories.
  • Content sanitization -- opt-in whitelist-based HTML sanitization with XSS, data: URI, and CDATA injection protection.
  • Input validation -- validates URLs, dates, enclosures, and RSS-specific fields with structured FeedValidationError errors.
  • XSLT stylesheets -- optional <?xml-stylesheet?> processing instruction for browser-friendly feeds.
  • WebSub hub -- declare a hub link for real-time push notifications across all formats.
  • Compact output -- toggle between pretty-printed and minified output.
  • Feed pagination -- built-in support for paginated feeds across all formats.
  • ESM + CJS -- dual module build with proper exports field and tree-shaking support.
  • Type-safe with full TypeScript support. Zero external dependencies.

Installation

npm install @bliztek/feed-generator
# or
pnpm add @bliztek/feed-generator

Quick Start

import { generateFeed } from "@bliztek/feed-generator";

const feed = {
  title: "My Blog",
  link: "https://example.com",
  description: "The latest articles from my blog.",
  items: [
    {
      id: "https://example.com/post-1",
      title: "First Post",
      link: "https://example.com/post-1",
      content: "<p>The full content of the first post.</p>",
      summary: "A summary of the first post.",
      date: "2024-12-14T10:00:00Z",
    },
  ],
};

// Generate a single format
const rss = generateFeed(feed, "rss");
const atom = generateFeed(feed, "atom");
const json = generateFeed(feed, "json");

Generate All Formats at Once

import { generateFeeds } from "@bliztek/feed-generator";

const { rss, atom, json } = generateFeeds(feed);

FeedBuilder (Fluent API)

import { FeedBuilder } from "@bliztek/feed-generator";

const rss = new FeedBuilder()
  .title("My Blog")
  .link("https://example.com")
  .description("The latest articles from my blog.")
  .language("en-us")
  .ttl(60)
  .addItem({
    id: "post-1",
    title: "First Post",
    link: "https://example.com/post-1",
    content: "<p>Hello world!</p>",
    date: "2024-12-14T10:00:00Z",
  })
  .generate("rss");

// Or generate all formats at once
const all = new FeedBuilder()
  .title("My Blog")
  .link("https://example.com")
  .description("Latest posts.")
  .addItem({ id: "1", title: "Post" })
  .generateAll();
// all.rss, all.atom, all.json

// Or get the raw Feed object
const feedData = new FeedBuilder()
  .title("My Blog")
  .link("https://example.com")
  .addItem({ id: "1", title: "Post" })
  .build();

Options

Both generateFeed and generateFeeds accept an optional options argument:

type FeedOptions = {
  sanitize?: boolean;    // Sanitize HTML content (default: false)
  pretty?: boolean;      // Pretty-print output (default: true)
  stylesheet?: string;   // XSLT stylesheet URL for RSS/Atom (ignored for JSON)
};

Content Sanitization

When sanitize: true is set, HTML content in items is processed through a whitelist-based sanitizer that:

  • Allows safe tags: <p>, <a>, <img>, <strong>, <em>, <ul>, <table>, and ~40 others
  • Strips with content: <script>, <style>, <svg>, <math>, <iframe>, <object>, <embed>, <applet>
  • Strips tags only (keeps text): any other tag not in the allowlist
  • Blocks dangerous URLs: javascript:, data:, and vbscript: in href, src, and action attributes
  • Removes event handlers: all on* attributes (onclick, onerror, etc.)
  • Filters attributes: only safe attributes are kept per tag (e.g., <a> keeps href, title, rel, target)
const safeFeed = generateFeed(feed, "rss", { sanitize: true });

Compact Output

// Minified XML/JSON -- no indentation or newlines
const compact = generateFeed(feed, "rss", { pretty: false });

Error Handling

All validation errors throw FeedValidationError with structured field and reason properties:

import { generateFeed, FeedValidationError } from "@bliztek/feed-generator";

try {
  generateFeed(feed, "rss");
} catch (e) {
  if (e instanceof FeedValidationError) {
    console.log(e.field);   // "title", "items[0].link", etc.
    console.log(e.reason);  // Human-readable description
  }
}

The following validations are performed automatically:

  • Feed title and link are required
  • Feed link must be a valid URL
  • RSS feeds require description
  • All URLs are validated (link, nextUrl, image.url, item link, enclosure url)
  • All dates are validated (updated, item date, item updated)
  • RSS enclosures require length
  • ttl must be a non-negative integer
  • skipHours values must be integers 0--23
  • skipDays values must be valid day names (Monday--Sunday)
  • Item image must be a valid URL
  • feedLinks.hub must be a valid URL
  • Extension xmlNamespaces keys must start with xmlns:
  • Extension jsonProperties keys must start with _ (JSON Feed spec)

Podcast Feeds

Generate podcast RSS feeds with full iTunes namespace support:

const podcastFeed = {
  title: "My Podcast",
  link: "https://example.com/podcast",
  description: "A podcast about tech.",
  authors: [{ name: "Jane Doe", email: "[email protected]" }],
  podcast: {
    type: "episodic",
    explicit: false,
    owner: { name: "Jane Doe", email: "[email protected]" },
    category: [
      { name: "Technology", subcategory: "Software How-To" },
      { name: "Education" },
    ],
    image: "https://example.com/podcast/cover.jpg",
  },
  items: [
    {
      id: "ep1",
      title: "Episode 1: Getting Started",
      link: "https://example.com/podcast/ep1",
      summary: "An introduction to the show.",
      date: "2024-12-14T10:00:00Z",
      enclosures: [
        {
          url: "https://example.com/podcast/ep1.mp3",
          type: "audio/mpeg",
          length: 50000000,
        },
      ],
      podcast: {
        duration: 1830,         // seconds, or "30:30" string
        episode: 1,
        season: 1,
        episodeType: "full",    // "full" | "trailer" | "bonus"
        explicit: false,
      },
    },
  ],
};

const rss = generateFeed(podcastFeed, "rss");
// Includes xmlns:itunes, <itunes:category>, <itunes:duration>, etc.

Podcast Types

type PodcastFeed = {
  type?: "episodic" | "serial";
  explicit?: boolean;
  owner?: { name: string; email: string };
  category?: string | { name: string; subcategory?: string }[];
  image?: string;
  newFeedUrl?: string;
  block?: boolean;
  complete?: boolean;
};

type PodcastItem = {
  duration?: number | string;  // seconds or "HH:MM:SS"
  explicit?: boolean;
  episode?: number;
  season?: number;
  episodeType?: "full" | "trailer" | "bonus";
  image?: string;
  block?: boolean;
};

Feed Pagination

All three formats support pagination via nextUrl:

const page1 = {
  title: "My Blog",
  link: "https://example.com",
  description: "Latest posts.",
  nextUrl: "https://example.com/feed?page=2",
  items: [/* ... */],
};

// RSS: <atom:link rel="next" href="..." />
// Atom: <link rel="next" href="..." />
// JSON: "next_url": "..."

Custom Extensions

Inject arbitrary XML namespaces and elements into RSS/Atom output, or custom properties into JSON Feed, without waiting for library support:

import { generateFeed, XmlNode } from "@bliztek/feed-generator";

const feed = {
  title: "My Blog",
  link: "https://example.com",
  description: "A blog with custom extensions.",
  extensions: {
    xmlNamespaces: {
      "xmlns:georss": "http://www.georss.org/georss",
    },
    xmlElements: [
      { tag: "georss:point", children: ["45.256 -71.92"] } as XmlNode,
    ],
    jsonProperties: {
      _custom_source: "my-cms",  // JSON Feed extensions must use _-prefixed keys
    },
  },
  items: [
    {
      id: "1",
      title: "Post",
      content: "<p>Hello</p>",
      extensions: {
        xmlElements: [
          { tag: "georss:point", children: ["40.714 -74.006"] } as XmlNode,
        ],
        jsonProperties: { _reading_time: 5 },
      },
    },
  ],
};

const rss = generateFeed(feed, "rss");
// RSS output includes xmlns:georss and <georss:point> elements

Validation enforces that XML namespace keys start with xmlns: and JSON Feed extension keys start with _ per the JSON Feed spec.


XSLT Stylesheets

Make RSS/Atom feeds render nicely in browsers by adding an XSLT stylesheet reference:

const rss = generateFeed(feed, "rss", { stylesheet: "/feed.xsl" });
// Output: <?xml version="1.0" encoding="UTF-8"?>
//         <?xml-stylesheet type="text/xsl" href="/feed.xsl"?>
//         <rss ...>

The stylesheet option is ignored for JSON Feed output.


WebSub Hub

Declare a WebSub hub for real-time push notifications of feed updates:

const feed = {
  title: "My Blog",
  link: "https://example.com",
  description: "A blog.",
  feedLinks: {
    rss: "https://example.com/rss.xml",
    hub: "https://hub.example.com",
  },
  items: [/* ... */],
};

// RSS: <atom:link rel="hub" href="https://hub.example.com" />
// Atom: <link rel="hub" href="https://hub.example.com" />
// JSON: "hubs": [{ "type": "WebSub", "url": "https://hub.example.com" }]

Feed Data Reference

Feed

| Field | Type | Required | Description | |---|---|---|---| | title | string | Yes | Feed title | | link | string | Yes | Site URL (validated) | | description | string | RSS only | Feed description (required for RSS) | | id | string | No | Feed ID (defaults to link) | | language | string | No | Language code (e.g., "en-us") | | copyright | string | No | Copyright notice | | updated | string \| Date | No | Last update (auto-derived from items if omitted) | | generator | string | No | Generator name | | image | FeedImage | No | Feed image/logo | | favicon | string | No | Favicon URL (JSON Feed) | | authors | FeedAuthor[] | No | Feed authors | | categories | FeedCategory[] | No | Feed categories | | items | FeedItem[] | Yes | Feed items/entries | | ttl | number | No | Time-to-live in minutes (RSS cache hint) | | skipHours | number[] | No | Hours (0--23) aggregators can skip (RSS) | | skipDays | string[] | No | Day names aggregators can skip (RSS) | | nextUrl | string | No | Next page URL for paginated feeds | | podcast | PodcastFeed | No | Podcast metadata (RSS only) | | feedLinks | object | No | Self-referencing URLs ({ rss?, atom?, json?, hub? }) | | extensions | FeedExtensions | No | Custom XML namespaces, elements, and JSON properties |

FeedItem

| Field | Type | Required | Description | |---|---|---|---| | id | string | Yes | Unique item identifier | | title | string | No | Item title | | link | string | No | Item URL (validated) | | content | string | No | HTML content | | contentText | string | No | Plain text content (JSON Feed) | | summary | string | No | Short summary | | date | string \| Date | No | Publication date (validated) | | updated | string \| Date | No | Last modified date (validated) | | authors | FeedAuthor[] | No | Item authors (all rendered in RSS) | | categories | FeedCategory[] | No | Item categories/tags | | image | string | No | Item image URL (RSS, Atom, JSON Feed) | | enclosures | FeedEnclosure[] | No | Attached media (podcasts, etc.) | | language | string | No | Item language (JSON Feed) | | podcast | PodcastItem | No | Podcast episode metadata (RSS only) | | extensions | FeedItemExtensions | No | Custom XML elements and JSON properties |

Supporting Types

type FeedAuthor = {
  name: string;
  url?: string;
  email?: string;
  avatar?: string;
};

type FeedCategory = {
  name: string;
  domain?: string;
  label?: string;
};

type FeedEnclosure = {
  url: string;
  type: string;       // MIME type
  length?: number;     // Size in bytes (required for RSS)
  title?: string;
  duration?: number;   // Duration in seconds
};

type FeedImage = {
  url: string;
  title?: string;
  link?: string;
  width?: number;
  height?: number;
};

API Reference

generateFeed(feed, format, options?): string

Generate a feed in a single format.

  • format: "rss" | "atom" | "json"
  • options: { sanitize?: boolean, pretty?: boolean, stylesheet?: string }
  • Returns: XML string (RSS/Atom) or JSON string
  • Throws: FeedValidationError if feed data is invalid

generateFeeds(feed, options?): FeedOutput

Generate all three formats at once.

  • Returns: { rss: string, atom: string, json: string }
  • Throws: FeedValidationError if feed data is invalid

FeedBuilder

Fluent builder for constructing feeds incrementally.

| Method | Description | |---|---| | .title(s) | Set feed title | | .link(s) | Set feed link | | .description(s) | Set feed description | | .id(s) | Set feed ID | | .language(s) | Set language code | | .copyright(s) | Set copyright notice | | .updated(s) | Set last update date | | .generator(s) | Set generator name | | .image(img) | Set feed image | | .favicon(s) | Set favicon URL | | .author(a) | Add an author (can be called multiple times) | | .category(c) | Add a category (can be called multiple times) | | .nextUrl(s) | Set pagination URL | | .ttl(n) | Set TTL in minutes | | .skipHours(h[]) | Set skip hours | | .skipDays(d[]) | Set skip days | | .podcast(p) | Set podcast metadata | | .feedLinks(links) | Set self-referencing URLs | | .hub(url) | Set WebSub hub URL | | .extensions(ext) | Set custom extensions | | .stylesheet(url) | Set XSLT stylesheet URL | | .addItem(item) | Add a single item | | .addItems(items) | Add multiple items | | .build() | Return the raw Feed object | | .generate(format, options?) | Render a single format | | .generateAll(options?) | Render all three formats |

FeedValidationError

Structured error class thrown on invalid feed data.

  • field: string -- path to the invalid field (e.g., "items[0].link")
  • reason: string -- human-readable description of the issue

Example Output

RSS

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>My Blog</title>
    <link>https://example.com</link>
    <description>The latest articles from my blog.</description>
    <item>
      <title>First Post</title>
      <link>https://example.com/post-1</link>
      <guid isPermaLink="true">https://example.com/post-1</guid>
      <description><![CDATA[<p>The full content of the first post.</p>]]></description>
      <content:encoded><![CDATA[<p>The full content of the first post.</p>]]></content:encoded>
      <pubDate>Sat, 14 Dec 2024 10:00:00 GMT</pubDate>
    </item>
  </channel>
</rss>

Atom

<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title type="text">My Blog</title>
  <link href="https://example.com" rel="alternate" type="text/html" />
  <id>https://example.com</id>
  <updated>2024-12-14T10:00:00.000Z</updated>
  <entry>
    <title type="text">First Post</title>
    <link href="https://example.com/post-1" rel="alternate" type="text/html" />
    <id>https://example.com/post-1</id>
    <published>2024-12-14T10:00:00.000Z</published>
    <content type="html">&lt;p&gt;The full content of the first post.&lt;/p&gt;</content>
    <summary type="text">A summary of the first post.</summary>
  </entry>
</feed>

JSON Feed

{
  "version": "https://jsonfeed.org/version/1.1",
  "title": "My Blog",
  "home_page_url": "https://example.com",
  "items": [
    {
      "id": "https://example.com/post-1",
      "url": "https://example.com/post-1",
      "title": "First Post",
      "content_html": "<p>The full content of the first post.</p>",
      "summary": "A summary of the first post.",
      "date_published": "2024-12-14T10:00:00.000Z"
    }
  ]
}

Feed Validation


Upgrading from 2.0

v2.1 is fully backwards compatible. No code changes are required.

What changes in output:

  • RSS feeds with item.content now include an additional <content:encoded> element alongside the existing <description>. The <description> element is unchanged.
  • RSS feeds with item.image now include <media:content> elements and the xmlns:media namespace declaration.
  • Atom feeds with item.image now include an enclosure <link> element.

These are additive changes. Existing feed consumers will not be affected. If you need the previous output exactly, pin to 2.0.x.

New optional features (opt-in, no effect unless used):

  • extensions on Feed and FeedItem for custom namespaces and elements
  • stylesheet in FeedOptions for XSLT support
  • feedLinks.hub for WebSub hub declarations

See the full CHANGELOG for details.


Contributing

Contributions are welcome! If you'd like to improve this package:

  1. Fork the repository.
  2. Create a new branch:
    git checkout -b feature-name
  3. Make your changes and commit:
    git commit -m "Add new feature"
  4. Push your branch and open a Pull Request.

More Information

This package is built and maintained by Bliztek. We design and develop customized websites and web applications to help businesses of all sizes achieve their goals. Our solutions prioritize scalability, security, and performance to drive growth and ensure long-term success.

  • Follow @Dev4TheWeb on Twitter/X for updates from the creator
  • Follow @Bliztek on Twitter/X for updates from the company side
  • Read our Company blog to learn more about our contributions to open source!
  • Read my Personal blog to learn more about what I do!

License

This package is licensed under the MIT License.