A blacksmith workshop forging data feeds with scrolls and warm lighting

Feedsmith: The Swiss Army Knife of Feed Parsing and Generation

The Orange Cat
The Orange Cat

Every web developer who has worked with syndication feeds knows the pain. RSS has multiple versions, Atom has its own quirks, JSON Feed showed up fashionably late, and RDF still lurks in the shadows. Most libraries force you to pick a side: parse or generate, one format or another. Feedsmith refuses to compromise. It parses and generates RSS, Atom, RDF, and JSON Feed formats in a single package, and it does so faster than just about anything else in the JavaScript ecosystem.

Whether you are building a podcast aggregator, an RSS reader, or a backend service that needs to both consume and produce feeds, feedsmith has you covered with comprehensive format support and over 30 recognized namespaces.

Why Feedsmith Stands Out

Most feed libraries specialize. rss-parser only parses. The feed package only generates. Feedsmith does both, and it does both well. Here is what makes it worth your attention:

  • Universal parsing that auto-detects RSS, Atom, RDF, and JSON Feed formats
  • Feed generation for RSS, Atom, and JSON Feed
  • OPML support for reading and working with outline files
  • 30+ namespace support including Dublin Core, iTunes, Media RSS, Podcast Index, YouTube, Spotify, and more
  • TypeScript-first with complete type definitions for every format
  • Lenient processing that gracefully handles malformed real-world feeds
  • Tree-shakable so you only bundle what you use
  • 2000+ tests with 99% code coverage

Getting It Into Your Project

Install feedsmith with your package manager of choice:

// npm
// npm install feedsmith

// yarn
// yarn add feedsmith

// pnpm
// pnpm add feedsmith

The package ships both CommonJS and ESM builds, supports Node.js 14+ and modern browsers, and can even be loaded from a CDN for quick prototyping.

Your First Feed, Parsed in Seconds

The Universal Approach

The simplest way to parse any feed is with parseFeed. It examines the content, figures out the format, and returns a structured result:

import { parseFeed } from 'feedsmith'

const response = await fetch('https://example.com/feed.xml')
const content = await response.text()

const { format, feed } = parseFeed(content)

console.log(`Detected format: ${format}`)
console.log(`Feed title: ${feed.title}`)
console.log(`Number of items: ${feed.items?.length}`)

The returned format string tells you whether you got an RSS, Atom, RDF, or JSON feed, and the feed object preserves the original structure of that format rather than flattening everything into a generic shape. This matters when you need access to format-specific fields or namespace data.

Going Format-Specific

When you know exactly what kind of feed you are dealing with, use the dedicated parsers for tighter types and slightly less overhead:

import { parseRssFeed, parseAtomFeed, parseJsonFeed } from 'feedsmith'

const rssFeed = parseRssFeed(rssXml)
console.log(rssFeed.channel?.title)
console.log(rssFeed.channel?.items?.[0]?.title)

const atomFeed = parseAtomFeed(atomXml)
console.log(atomFeed.title)
console.log(atomFeed.entries?.[0]?.title)

const jsonFeed = parseJsonFeed(jsonString)
console.log(jsonFeed.title)
console.log(jsonFeed.items?.[0]?.title)

Each parser returns an object shaped exactly like the corresponding specification, so RSS gives you channel and items, Atom gives you entries, and JSON Feed follows the JSON Feed spec structure.

Reading OPML Files

OPML files are commonly used to export and import feed subscription lists. Feedsmith handles those too:

import { parseOpml } from 'feedsmith'

const opml = parseOpml(opmlContent)
console.log(`Subscription list: ${opml.head?.title}`)

for (const outline of opml.body?.outlines ?? []) {
  console.log(`${outline.text}: ${outline.xmlUrl}`)
}

Forging Feeds From Scratch

Generating an RSS Feed

One of Feedsmith's standout features is feed generation. Creating a valid RSS feed takes just a few lines:

import { generateRssFeed } from 'feedsmith'

const rss = generateRssFeed({
  title: 'The Developer Dispatch',
  link: 'https://example.com',
  description: 'Weekly insights from the trenches of web development',
  language: 'en',
  lastBuildDate: new Date(),
  items: [
    {
      title: 'Why TypeScript Won',
      link: 'https://example.com/posts/typescript-won',
      description: 'A look at how TypeScript conquered the JavaScript world.',
      pubDate: new Date('2026-03-01'),
      guid: 'https://example.com/posts/typescript-won',
    },
    {
      title: 'Edge Computing in 2026',
      link: 'https://example.com/posts/edge-computing',
      description: 'The state of running code at the edge.',
      pubDate: new Date('2026-02-25'),
      guid: 'https://example.com/posts/edge-computing',
    },
  ],
})

// rss is a valid XML string ready to serve

Building an Atom Feed

Atom feeds follow a similar pattern with format-appropriate fields:

import { generateAtomFeed } from 'feedsmith'

const atom = generateAtomFeed({
  id: 'urn:uuid:blog-feed-001',
  title: 'The Developer Dispatch',
  updated: new Date(),
  authors: [{ name: 'Jane Developer' }],
  links: [{ href: 'https://example.com', rel: 'alternate' }],
  entries: [
    {
      id: 'urn:uuid:post-001',
      title: 'Why TypeScript Won',
      updated: new Date('2026-03-01'),
      content: {
        type: 'html',
        value: '<p>A deep dive into TypeScript dominance.</p>',
      },
    },
  ],
})

Crafting a JSON Feed

JSON Feed generation is just as straightforward:

import { generateJsonFeed } from 'feedsmith'

const json = generateJsonFeed({
  version: 'https://jsonfeed.org/version/1.1',
  title: 'The Developer Dispatch',
  home_page_url: 'https://example.com',
  feed_url: 'https://example.com/feed.json',
  items: [
    {
      id: 'post-001',
      title: 'Why TypeScript Won',
      content_html: '<p>TypeScript took over.</p>',
      date_published: '2026-03-01T00:00:00Z',
    },
  ],
})

Taming Namespaces and Messy Feeds

Working with Podcast and Media Namespaces

Real-world feeds are packed with namespace extensions. Feedsmith recognizes over 30 namespaces and normalizes them into clean, typed objects:

import { parseRssFeed } from 'feedsmith'

const podcastFeed = parseRssFeed(podcastXml)
const episode = podcastFeed.channel?.items?.[0]

// iTunes namespace fields
console.log(episode?.itunes?.duration)
console.log(episode?.itunes?.explicit)
console.log(episode?.itunes?.image?.href)

// Media RSS namespace
console.log(episode?.media?.content?.[0]?.url)
console.log(episode?.media?.content?.[0]?.type)

// Podcast Index namespace
console.log(episode?.podcast?.transcript?.[0]?.url)

Feedsmith handles custom namespace prefixes gracefully. Even if a feed uses a non-standard prefix, the library maps it to the correct namespace based on the URI.

Handling the Real World

Feeds in the wild are messy. Fields appear in the wrong case, dates come in bizarre formats, and some feeds barely qualify as valid XML. Feedsmith takes a lenient approach:

import { parseFeed } from 'feedsmith'

// Feedsmith handles all of these gracefully:
// - Case-insensitive field matching
// - Non-standard namespace URIs
// - Missing required fields
// - Mixed RSS versions
const { feed } = parseFeed(messyFeedContent)

// You still get a clean, typed result
console.log(feed.title)

This tolerance for imperfection makes Feedsmith practical for production use where you cannot control the quality of incoming feeds.

Bringing Type Safety to Feeds

Feedsmith ships complete TypeScript definitions for every supported format:

import type { Rss, Atom, Json, Opml } from 'feedsmith/types'

function processRssFeed(feed: Rss.Feed): void {
  const channel = feed.channel
  if (channel?.items) {
    for (const item of channel.items) {
      console.log(item.title, item.pubDate)
    }
  }
}

function processAtomFeed(feed: Atom.Feed): void {
  if (feed.entries) {
    for (const entry of feed.entries) {
      console.log(entry.title, entry.updated)
    }
  }
}

Having distinct types for each format means your editor catches mistakes before runtime, and you get full autocompletion for format-specific fields and namespaces.

A Note on Speed

Benchmarks show Feedsmith running roughly 3x faster than rss-parser on large RSS files, 2.5x to 5.9x faster on Atom feeds, and even outpacing feed parsers written in Go, Ruby, and Python. For applications processing hundreds or thousands of feeds, that performance gap translates directly into lower server costs and faster response times.

Wrapping Up

feedsmith fills a gap that has existed in the JavaScript ecosystem for a long time. Instead of cobbling together one library for parsing and another for generation, or wrestling with incomplete namespace support, you get a single package that handles everything. It is fast, well-tested, TypeScript-native, and forgiving enough to handle the messy reality of feeds in the wild. If your project touches syndication in any way, Feedsmith deserves a spot in your dependencies.