GuidesFEEDS

A free rss2json alternative

You can replace rss2json with proxy.cors.dev plus a 30-line DOMParser function that returns the same status, feed and items JSON for RSS 2.0 and Atom feeds. The free proxy needs no signup or key, allows GET and HEAD at about 600 requests a minute per IP, and returns every item in the feed up to 6 MiB, while rss2json's Free plan returns 10 items unless you pass a key and allows 10,000 requests a day. On 28 October 2026 every request we sent to api.rss2json.com got a Cloudflare challenge page with HTTP 403.

What rss2json offers

rss2json converts an XML feed into JSON at https://api.rss2json.com/v1/api.json?rss_url=<URL-encoded feed URL>. The api_key parameter is optional, though the count parameter (which defaults to 10 items), order_by and order_dir only work if you pass an api_key. You can also pass a callback parameter to get JSONP instead of standard JSON.

When a request succeeds, the response returns status set to "ok", a feed object (url, title, link, author, description, image) and an items array (title, pubDate, link, guid, author, thumbnail, description, content, enclosure, categories).

rss2json.com/plans, as archived on 18 October 2026, lists three plans. Paid plans can be billed for 1, 3, 6 or 12 months at 0, 3, 5 or 10 percent off.

rss2json plans, checked October 2026
PlanPriceRequests a dayFeedsFeed refresh
rss2json's Free plan$010,00025every 60 minutes
rss2json's Basic plan$4 a month1,000,0001,500every 15 minutes
rss2json's Pro plan$8 a month2,000,0004,500every 5 minutes

rss2json from the browser on 28 October 2026

During a probe on 28 October 2026, every request to api.rss2json.com, along with the rss2json.com home, /plans and /docs pages, returned a Cloudflare "Just a moment..." challenge page with HTTP 403. That happened from a server, through cors.dev, from 3 residential IP addresses sending Chrome request headers, and inside a real headless Firefox browser.

Because the Cloudflare challenge page doesn't include an Access-Control-Allow-Origin header, the browser blocks the response before your code can inspect it. Your fetch() call rejects with a TypeError instead of returning JSON or a readable 403 status. A fetch() cannot complete a Cloudflare challenge, so while rss2json serves one, browser calls fail too.

Convert RSS to JSON in the browser

You can replace rss2json by fetching the raw XML feed through https://proxy.cors.dev/<feed URL> and parsing it in the browser with DOMParser. The feedToJson() function below takes 30 lines of JavaScript, returns the same status, feed and items shape as rss2json for RSS 2.0, RSS 1.0 (RDF) and Atom feeds, and throws "Not an RSS or Atom feed" for anything else.

feedToJson(): RSS or Atom to rss2json-style JSON
const PROXY = 'https://proxy.cors.dev/';

async function feedToJson(feedUrl) {
  const res = await fetch(PROXY + feedUrl);
  if (!res.ok) throw new Error(`Feed answered ${res.status} ${res.headers.get('X-Cors-Error') ?? ''}`);
  const root = new DOMParser().parseFromString(await res.text(), 'text/xml').documentElement;
  if (!['rss', 'feed', 'RDF'].includes(root.localName)) throw new Error('Not an RSS or Atom feed');

  const atom = root.localName === 'feed';
  const text = (el, ...tags) => tags.map((t) => el.getElementsByTagName(t)[0]?.textContent.trim()).find(Boolean) ?? '';
  const href = (el, rel) => el.querySelector(`link[rel="${rel}"]`)?.getAttribute('href') ?? '';
  const link = (el) => (atom ? href(el, 'alternate') || el.querySelector('link')?.getAttribute('href') || '' : text(el, 'link'));
  const channel = atom ? root : root.querySelector('channel');
  return {
    status: 'ok',
    feed: { url: feedUrl, title: text(channel, 'title'), link: link(channel), description: text(channel, 'description', 'subtitle') },
    items: [...root.querySelectorAll(atom ? 'entry' : 'item')].map((item) => ({
      title: text(item, 'title'),
      pubDate: text(item, 'pubDate', 'published', 'updated', 'dc:date'),
      link: link(item),
      guid: text(item, 'guid', 'id'),
      author: text(item, 'dc:creator', 'name', 'author'),
      description: text(item, 'description', 'summary'),
      content: text(item, 'content:encoded', 'content'),
      enclosure: item.querySelector('enclosure')?.getAttribute('url') ?? href(item, 'enclosure'),
      categories: [...item.getElementsByTagName('category')].map((c) => c.getAttribute('term') ?? c.textContent.trim()),
    })),
  };
}
console.log(await feedToJson('https://github.blog/feed/'));

To put this in place:

  1. Check if the feed needs a proxy

    If a feed already sends Access-Control-Allow-Origin, drop the proxy prefix and fetch it directly. NASA's WordPress feed sends Access-Control-Allow-Origin: *, for example, whereas BBC News and hnrss.org send none.

  2. Fetch and parse the XML

    Pass the target URL to feedToJson(), which prepends https://proxy.cors.dev/ (such as https://proxy.cors.dev/https://example.com/feed) and runs the response through DOMParser.

  3. Render the items safely

    The description and content fields contain HTML written by a third party. Sanitize them before inserting them into your page, or render them as plain text; our guide to fetching and parsing RSS feeds covers safe rendering.

On 28 October 2026, we tested feedToJson() against several live feeds:

  • WordPress News in RSS 2.0 (10 items) and Atom (10 entries)
  • The GitHub Blog (10 items) and GitHub releases in Atom (10 entries)
  • A YouTube channel's Atom feed (15 entries)
  • hnrss.org (20 items) and a Substack newsletter (20 items)
  • BBC News (32 items)
  • Slashdot (15 items) and Debian News (6 items) in RSS 1.0
  • A plain HTML page, which threw "Not an RSS or Atom feed" as intended

How RSS and Atom fields map to JSON

RSS and Atom name several elements differently. feedToJson() normalizes them into the object properties that rss2json returns.

Feed fields to rss2json-style JSON, checked October 2026
JSON fieldRSS 2.0 sourceAtom source
titleitem titleentry title
pubDatepubDate (dc:date in RSS 1.0)published, else updated
linklink element texthref of link rel="alternate"
guiditem guidentry id
authordc:creator or authorauthor name
descriptionitem descriptionentry summary
contentcontent:encodedentry content
enclosureenclosure url attributehref of link rel="enclosure"
categoriescategory textcategory term attribute

rss2json also returns an item thumbnail field along with the feed author and image. feedToJson() leaves those out, but you can add them with one line each if your layout uses them. RSS 1.0 items have no guid, so that field comes back empty for them.

For pubDate, feedToJson() keeps the feed's own string format: RFC 822 in RSS ("Wed, 28 Oct 2026 17:13:02 +0000") and ISO 8601 in Atom ("2026-10-28T17:13:02Z"). JavaScript's new Date() parses both formats without extra code.

Free limits side by side

rss2json Free plan and cors.dev free tier, checked October 2026
Limitrss2json Free plancors.dev free tier
Items per feed10 without a keyEvery item in the feed up to 6 MiB
Requests10,000 a dayAbout 600 a minute per IP, no daily quota
Feeds25Any
FreshnessCached, refreshed every 60 minutesFetched live on every request
API keyOptional, needed for count and orderingNone
OutputJSON or JSONPFeed XML converted by your own code

The cors.dev free tier requires no signup and no key. It supports GET and HEAD requests at up to about 600 requests a minute per IP, answering 429 with a Retry-After header past that limit, and has no daily or monthly quota. Each upstream request has a 30-second deadline and can return up to 6 MiB (6,291,456 bytes); larger responses fail with HTTP 502 and the header X-Cors-Error: response_too_large.

Because the free tier doesn't cache responses, every page view fetches the feed live. cors.dev Pro ($5 a month for 500,000 requests, with a 7-day trial of 1,000 requests and no card) can cache a GET for up to 300 seconds with the X-Cors-Cache header when the upstream response allows it. On 28 October 2026 the GitHub Blog feed (max-age=300) qualified for the full 300 seconds, BBC News (max-age=2) capped it at 2 seconds, and Substack feeds (Set-Cookie, no-cache) are never cached. Cache hits still count as requests.

Good questions.

Why is rss2json throwing a CORS TypeError in my browser?

On 28 October 2026, requests to api.rss2json.com returned an HTTP 403 Cloudflare challenge page instead of JSON. Because that challenge response doesn't include an Access-Control-Allow-Origin header, the browser blocks your script from reading it and fetch() rejects with a TypeError.

How do I get more than 10 items from an RSS feed without an API key?

rss2json's Free plan defaults to 10 items per feed unless you pass an api_key with the count parameter. When you fetch the XML through https://proxy.cors.dev/<feed URL> and run it through feedToJson(), you get every item published in the feed file up to the 6 MiB response cap. In our 28 October 2026 tests, that returned 15 entries from YouTube, 20 items from Substack and hnrss.org, and 32 items from BBC News.

How do I convert an Atom or RSS 1.0 feed to JSON in JavaScript?

Use the same feedToJson() function: it detects RSS 2.0, RSS 1.0 (RDF) and Atom from the root element and maps format-specific elements such as RSS 1.0's dc:date and Atom's published, summary and link rel="alternate" to rss2json's property names. On 28 October 2026 it returned 15 items from Slashdot's RSS 1.0 feed and 10 entries from the WordPress News Atom feed. Anything else throws "Not an RSS or Atom feed".

Can I cache feed responses with cors.dev so my site does not fetch the XML on every page view?

The free tier fetches the feed live on every request and caches nothing. On cors.dev Pro, X-Cors-Cache: 300 caches a GET for up to 300 seconds when the upstream allows it: status 200, no Set-Cookie, no private, no-store or no-cache, no key or token in the query string, and a TTL capped by the feed's own max-age. X-Cors-Cache-Status and X-Cors-Cache-Reason tell your code whether a response was stored, and cache hits still count toward your 500,000 monthly requests.

What else is included in cors.dev Pro if I upgrade?

For $5 a month, cors.dev Pro gives you 500,000 requests, a throughput of 3,000 requests a minute per account with 100 in flight, and only answers your exact allowed origins. It also adds POST, PUT, PATCH and DELETE methods, request bodies up to 1 MiB, and forwards Authorization and custom headers, while keeping the same 6 MiB response cap and 30-second deadline as the free tier. Note that cors.dev Pro does not hide an API key: anything your browser code sends is visible to anyone using the page.