Feedsmith, a feed parser that normalizes legacy RSS into modern fields
Fast, all‑in‑one JavaScript feed parser and generator for RSS, Atom, RDF, and JSON Feed, with support for popular namespaces and OPML.
At a glance
- What is it?
- Feedsmith is a TypeScript library that parses and generates RSS, Atom, RDF, JSON Feed and OPML, mapping old elements onto their modern equivalents and tolerating the namespace and casing mistakes real feeds contain. Version 3 rewrote the API around per-format parsers, and the migration guide is the first thing to read.
- Who is it for?
- Feedsmith suits a TypeScript or Node project that reads and writes feeds, particularly one that has been bitten by a legacy feed using Dublin Core where the code expected a modern field, and it suits a browser bundle where tree-shaking matters. It does not suit a project that needs RDF output, since generating RDF is marked as planned rather than available, and it does not suit a team that has to stay on 2.x, where the 2-to-v3 migration guide is the work item.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
One package for reading feeds and writing them
Feedsmith is a JavaScript library for feed parsing and feed generation, and the point of it is that both halves live in the same package. The project describes it as a fast, all-in-one feed parser and generator for RSS, Atom, RDF and JSON Feed, with support for popular namespaces and OPML files.
That combination matters more than it sounds. A project that both consumes feeds and publishes them usually ends up with two libraries, two type definitions and two sets of quirks. Feedsmith's core feature list names this directly: parsing and generating with one package.
The audience is a JavaScript or TypeScript developer building something that reads feeds, writes feeds, or does both. The compatibility section says it works in Node.js and modern browsers, supports both CommonJS and ES modules, and works with plain JavaScript, so you do not need TypeScript to use it.
The repository is a single package with a `src/` directory, a `benchmarks/` directory and a `docs/` directory served by VitePress. The build uses tsdown, and the runtime dependencies are deliberately minimal: `fast-xml-parser` for the XML and `entities` for entity handling. Nothing else.
The package manifest also sets `"sideEffects": false`, which is the flag that makes the tree-shaking claim work in a bundler. Combined with the documented tree-shakable feature, it means a browser application that only needs RSS parsing does not ship the Atom or JSON Feed code paths.
The engine field is `node >= 14`, and the exports map is written out to give separate type declaration files for the import and require conditions, which is the detail that makes it usable from a TypeScript project that mixes module systems.
Legacy elements are mapped onto their modern equivalents
The design decision that distinguishes Feedsmith is in the leniency section, and it is worth understanding before you look at any API.
Feedsmith offers universal and format-specific parsers, and the project states that each parser turns a feed into an object that mirrors its original structure and maps legacy elements to their modern equivalents, so that you read an old feed the same way as a new one.
Concretely, the feature list names this as normalizing legacy elements, upgrading feed elements to their modern equivalents so that you never need to worry about reading feeds in older formats. In practice that means a feed that puts an author in a Dublin Core `dc:creator` element, where an Atom feed would use `author`, still gives you the same shape of answer.
The trade-off is that the parsed object no longer mirrors the wire format exactly. The parsed feed object maintains the original feed structure, which is a different claim, and the mapping sits on top of it. If your application needs to round-trip a feed byte-for-byte, or needs to know which of two representations the author used, this library is the wrong shape. If your application needs to read `item.author` and not care, it is exactly right.
There is a second normalization layer for namespaces. The smart namespace handling feature normalizes custom namespace prefixes to standard ones, and the example given is that a `<custom:creator>` element becomes `dc.creator`. That is the same idea applied to prefixes rather than elements: a feed that invents a prefix for the Dublin Core namespace gets read as Dublin Core anyway.
Leniency is about surviving what real feeds contain
The leniency features are what decide whether a parser works on the web rather than in a test fixture.
Case insensitivity is explicit, with the spelling the project uses, handling fields and attributes in any case, lowercase, uppercase or mixed. Namespace URI tolerance means the parser accepts non-official namespace URIs, with HTTPS variants, case variations, trailing slashes and whitespace all tolerated. This matters more than it sounds: a feed that declares `http://purl.org/dc/elements/1.1/` where the spec says `http://purl.org/dc/elements/1.1/` with a different trailing character, or that uses a slightly wrong URL, is extremely common, and a strict parser rejects the whole feed for it.
Then there is forgiveness, which the documentation frames as gracefully handling malformed or incomplete feeds and extracting valid data, and it says this makes the library suitable for use with real-world feeds that may not strictly follow specifications.
The parser is built on fast-xml-parser, and the architecture that produces this behaviour is visible in the package layout rather than hidden. The src/ directory contains per-format modules, and the documentation lists separate reference pages for each feed format and each namespace, which is the shape of a codebase where each format has its own parser and its own generator.
A fair caveat: the leniency has a limit that the documentation does not spell out. A parser that extracts valid data from a malformed feed will happily give you a partial object, and partial objects that look complete are harder to debug than errors. If you are ingesting feeds where a missing field must fail loudly, the forgiving behaviour works against you and you need your own validation on top.
The format matrix, including the gap in RDF generation
The support tables are precise about what exists and what does not, and one cell in them matters.
For feeds, RSS at versions 0.9x and 2.0 is available for both parse and generate, Atom at 0.3 and 1.0 for both, JSON Feed at 1.0 and 1.1 for both, and RDF at 0.9 and 1.0 for parse only, with generation marked as planned. OPML at versions 1.0 and 2.0 is available for both parse and generate.
So the gap is RDF output. If you consume RDF, which still appears in the wild, you can read it. If you publish RDF, this library will not do it yet, and you need a different tool for that side.
The namespace table is far longer, and it is the reason to choose this library over a general XML parser. Available for both parse and generate: Atom, Dublin Core, Dublin Core Terms, Syndication, Content, Slash, iTunes, Podlove Simple Chapters, Media RSS, Google Play Podcast, FeedBurner, FeedPress, arXiv, OpenSearch, PRISM, ccREL, Creative Commons, Atom Threading, Comment API, Administrative, Pingback, Trackback, Source, blogChannel, and W3C Basic Geo.
The ones that vary by direction are worth noting. The Podcast Index namespace, Spotify, Acast and RawVoice are RSS only for parsing, and the Atom Publishing Protocol and YouTube namespaces are Atom only. The full list runs past the point where the documentation excerpt ends, at the W3C Basic Geo and GeoRSS Simple entries, and the project states the aim is to fully support all major feed formats and namespaces in complete alignment with their specs.
For a podcast client or an aggregator, the iTunes, Podcast Index, Podlove Simple Chapters and Google Play Podcast entries are the reason this library exists rather than a generic XML to JSON helper.
{ "heading": "One entry point per condition, resolved by the exports map", "body": "The package manifest is short enough to read in one go, and the part that matters for integration is the exports map.
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
},
"./package.json": "./package.json"
},This is the shape a dual CommonJS and ES module package needs in order to give TypeScript the right declaration file for each condition. The import branch resolves to the ES module build with an .mts declaration, and the require branch to the CommonJS build with a .cts declaration, and both are produced by a single build command.
"build": "tsdown src/index.ts --format cjs,esm --dts --clean --unbundle",The `--unbundle` flag is the setting that matches the tree-shaking promise. It keeps the module graph rather than collapsing it into one file, so a bundler can drop the parts of the library your application never imports, and `--clean` wipes the previous output so stale files cannot survive a rename.
For a first real use, the practical order is: add feedsmith to the project, import the parser for the format you expect rather than the universal one if you know it, and read the item fields through the normalized shape, so that a legacy feed and a modern one give you the same property names. The universal parser is the fallback when you do not control the input.
The published files list is just `dist`, so nothing but the built output ships to npm, and the engine requirement is `node >= 14`, which keeps the library usable in older runtimes than a modern build pipeline would suggest.", }
Bundle size and the claims around speed
Two performance claims are made, and they are worth separating because they mean different things.
The first is the tree-shaking one, which we already touched on. It is a build-time property, controlled by the `sideEffects: false` flag in the manifest and the modular per-format source layout, and it is verifiable from the package structure without running anything. If you only import the RSS parser, the Atom and JSON Feed code does not reach your bundle.
The second is a speed claim, described as one of the fastest JavaScript feed parsers, with a link to the project's benchmarks page and a `benchmarks/` directory in the repository. The documentation states the test suite has over 4000 tests and 99% code coverage.
The honest caveat is that the benchmark is the project's own, and the documentation does not describe the methodology, the feed corpus or the comparison set. Feed parsing speed depends heavily on the input, since a large feed with many namespaces and nested elements exercises the XML layer differently from a small one. What you can take from the claim is that the library is written for speed, with a fast XML parser underneath and tree-shaking at the boundary.
The dependency count supports the performance story in a way that is easy to verify. Two runtime dependencies, `fast-xml-parser` and `entities`, and nothing else. There is no date library, no HTML parser and no sanitizer in the dependency tree, which means the library parses feeds rather than processing arbitrary documents, and it means the supply chain surface is small.
One thing to check before you rely on the numbers: the claim is about parsing, and the library also generates. Whether the generator is equally fast is a separate question the documentation does not answer.
Version 3 is a rewrite, and the migration guide is the documentation
The release history shows a 3.0 landing in September 2026, and the project marks it prominently. The documentation states that Feedsmith 3.0 is out, that it comes with a range of improvements and some breaking changes, and points to a migration guide for what is new and how to upgrade from 2.x.
The release record backs up the scale of the change. v3.0.0-rc.3 was tagged in August 2026, v3.0.0 on 2026-09-18 and v3.0.1 on 2026-09-25, so the 3.0 line settled within about a week of the major release and has had a patch release since. The last push to the repository was on 2026-10-13.
The structural change is the move to per-format parsers. The 2.x API was a single entry point with format detection, and 3.0 splits it into format-specific parsers alongside the universal one. That is the right design for a library that wants per-format tree-shaking and per-format type definitions, and it is also the change that will break your code, because the import paths and the call signatures move.
The licence is MIT, with the LICENSE file at the top of the repository, so commercial use, modification and redistribution are all fine with the notice retained.
On the engineering side, the repository uses bun as its package manager, with a bun.lock file, Biome for linting and formatting through biome.json, lefthook for git hooks through lefthook.json, and commitlint for commit message conventions through commitlint.json. The docs site is VitePress, with `docs:dev` and `docs:build` scripts, and the prepare script installs lefthook. The engine requirement of Node 14 or newer is conservative, which matters if you run this in an older runtime than your build tooling.
Editorial conclusion
Feedsmith suits a TypeScript or Node project that reads and writes feeds, particularly one that has been bitten by a legacy feed using Dublin Core where the code expected a modern field, and it suits a browser bundle where tree-shaking matters. It does not suit a project that needs RDF output, since generating RDF is marked as planned rather than available, and it does not suit a team that has to stay on 2.x, where the 2-to-v3 migration guide is the work item. Verify first that the namespaces your feeds actually use are in the supported list, that the 3.0 migration guide has no surprises for your call sites, and that the field names your code reads still exist after the legacy-to-modern mapping is applied.
Frequently asked questions
What does Feedsmith parse and generate?
RSS at versions 0.9x and 2.0, Atom at 0.3 and 1.0, JSON Feed at 1.0 and 1.1, and OPML at 1.0 and 2.0 are all supported for both parsing and generating. RDF at versions 0.9 and 1.0 is supported for parsing, with generation marked as planned.
How does Feedsmith handle legacy feed elements?
Its parsers map legacy elements onto their modern equivalents, so an old feed reads the same way as a new one, and custom namespace prefixes are normalized to standard ones, with a custom:creator element becoming dc.creator for example. The parsed object mirrors the original feed structure with that mapping applied on top.
Is Feedsmith tolerant of malformed feeds and namespace errors?
Yes. The project documents case-insensitive handling of fields and attributes, namespace URI tolerance for HTTPS variants, case variations, trailing slashes and whitespace, and forgiving handling of malformed or incomplete feeds where valid data is still extracted.
What broke when Feedsmith moved to version 3?
The documentation marks 3.0 as coming with breaking changes and points to a migration guide for what is new and how to upgrade from 2.x. The structural change is the move to format-specific parsers alongside the universal parser, which changes import paths and call signatures.
What are Feedsmith's runtime dependencies and licence?
Two runtime dependencies, fast-xml-parser and entities, and nothing else. The licence is MIT, with the LICENSE file at the top of the repository. The package also sets sideEffects to false so that bundlers can tree-shake the parts you do not import.
Official sources
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.
[](https://hysenlabs.com/projects/macieklamberski-feedsmith)Was this useful?