GuidesFEEDS
WordPress RSS feed and REST API
A WordPress site serves RSS 2.0 at /feed/, Atom at /feed/atom/, and the same feed per category, tag, author and comment thread. The REST API at /wp-json/wp/v2/posts echoes your origin in Access-Control-Allow-Origin by default, so call it directly, no proxy, for up to 100 posts per request. Feeds get no CORS headers from WordPress core, and 4 of the 5 live feeds we tested on 28 October 2026 failed cross-origin; read those through the free proxy.cors.dev (no signup, GET and HEAD, about 600 requests a minute per IP).
WordPress feed URLs
WordPress serves a feed at /feed/ appended to the home page and to most archive URLs: a category, a tag, an author or a single post. On a subdirectory install such as wordpress.org/news, the paths start at https://wordpress.org/news, so the releases category feed is https://wordpress.org/news/category/releases/feed/.
| Feed | Path | Test on wordpress.org/news |
|---|---|---|
| Latest posts, RSS 2.0 | /feed/ | 10 items, 87,418 bytes |
| Latest posts, Atom | /feed/atom/ | 10 entries, 88,822 bytes |
| All comments | /comments/feed/ | 10 items, 9,408 bytes |
| One post's comments | <post URL>/feed/ | 0 items, 833 bytes |
| Category | /category/<slug>/feed/ | 10 items, 180,753 bytes |
| Tag | /tag/<slug>/feed/ | 3 items, 17,931 bytes |
| Author | /author/<username>/feed/ | 10 items, 201,552 bytes |
| Search | /?s=<term>&feed=rss2 | 64,530 bytes |
| Older posts | /feed/?paged=2 | 10 items, 302,869 bytes |
| No pretty permalinks | /?feed=rss2 | 10 items, 87,245 bytes |
The 28 October 2026 tests used the releases category, the security tag, the author matt and the search term release.
Each site sets its own feed length under Settings, Reading in the WordPress admin. In our tests, wordpress.org/news and nasa.gov returned 10 posts per feed request, techcrunch.com returned 20, and ma.tt returned 25.
Which WordPress endpoints work cross-origin
WordPress core handles CORS differently for RSS feeds and the REST API. Core's rest_send_cors_headers() function runs on REST API routes, where it echoes the browser's Origin header in Access-Control-Allow-Origin and adds Access-Control-Allow-Credentials: true. It doesn't run on feed URLs, so unless a site adds its own headers at the web server or CDN layer, browser fetch() calls to /feed/ from another origin get blocked.
We tested 6 WordPress sites on 28 October 2026 by sending an Origin header to both /feed/ and /wp-json/wp/v2/posts.
| Site | Feed CORS header | Feed from another origin | REST API /wp-json/wp/v2/posts |
|---|---|---|---|
| wordpress.org/news | none | needs a proxy | 200 with origin echoed |
| techcrunch.com | https://techcrunch.com only | needs a proxy | 200 with origin echoed |
| nasa.gov | * | works directly | 200 with origin echoed |
| ma.tt | none | needs a proxy | 200 with origin echoed |
| css-tricks.com | none | needs a proxy | 401 rest_authorization_required |
| whitehouse.gov | feed returns 404 | no feed | 403 REST disabled |
Of the 5 live feeds, 4 couldn't be read cross-origin from the browser directly: wordpress.org/news, ma.tt, and css-tricks.com sent no Access-Control-Allow-Origin header at all, while techcrunch.com sent Access-Control-Allow-Origin: https://techcrunch.com, which only lets its own pages read the response. Only nasa.gov sent Access-Control-Allow-Origin: *.
On the REST side, 4 of the 6 sites answered 200 with the caller's origin echoed back. css-tricks.com answered 401 (rest_authorization_required, because REST is limited to logged-in users there) with CORS headers, so your JavaScript can inspect the status code and fall back. whitehouse.gov answered 403 (REST disabled) without CORS headers, so a direct fetch() rejects with a TypeError instead.
Use the REST API first
Because WordPress enables CORS on /wp-json/wp/v2/posts by default, your browser code should try the REST API directly before touching the RSS feed. On sites without pretty permalinks, swap /wp-json/wp/v2/posts for /?rest_route=/wp/v2/posts (which also returned 200 on wordpress.org/news).
The endpoint takes query parameters to trim and filter the payload:
_fields: return only the JSON fields you name. 10 posts fromwordpress.org/newswith_fields=date_gmt,link,titlecame to 1,823 bytes, against 114,896 bytes for the same 10 posts without_fieldsand 87,418 bytes for/feed/._embed: include linked resources like authors and featured images in the same response.per_page,page,offset,search,categories,tags,author,orderby: control paging, filtering, and sort order.
To filter by category through REST, you need the numeric category ID rather than the slug:
- Look up the category ID
Request
/wp-json/wp/v2/categories?slug=releases&_fields=id,name,count(which returned a 41-byte payload in our test) and read theidfield. - Fetch the posts
Pass that ID to
/wp-json/wp/v2/posts?categories=<id>.
Watch out for two quirks in the JSON response. First, title.rendered contains HTML entities like –, so decode it before rendering it into text nodes. Second, date_gmt comes back without a time zone suffix, so append Z to the string before passing it to new Date().
Fall back to the RSS feed through cors.dev
When a site locks down /wp-json/wp/v2/posts like css-tricks.com does, fall back to /feed/ through https://proxy.cors.dev/. Put the target feed URL right after the proxy base URL, such as https://proxy.cors.dev/https://ma.tt/feed/. Every live feed in our tests returned 200 through https://proxy.cors.dev/ with the caller's origin in Access-Control-Allow-Origin.
The wordpressPosts(site, page) helper below tries /wp-json/wp/v2/posts directly with _fields=date_gmt,link,title and per_page=10, reads X-WP-TotalPages, and only fetches /feed/?paged=N through proxy.cors.dev if the REST request fails or returns an error status.
const PROXY = 'https://proxy.cors.dev/';
const decode = (html) => new DOMParser().parseFromString(`<body>${html}`, 'text/html').body.textContent;
async function wordpressPosts(site, page = 1) {
// The REST API sends CORS headers by default, so we try it first without the proxy
const api = await fetch(`${site}/wp-json/wp/v2/posts?per_page=10&page=${page}&_fields=date_gmt,link,title`).catch(() => null);
if (api?.ok) {
const posts = await api.json();
return {
totalPages: Number(api.headers.get('X-WP-TotalPages')),
posts: posts.map((p) => ({ title: decode(p.title.rendered), link: p.link, date: new Date(`${p.date_gmt}Z`) })),
};
}
// REST is disabled or locked, so we read the RSS feed through the proxy
const res = await fetch(`${PROXY}${site}/feed/?paged=${page}`);
if (!res.ok) throw new Error(`REST API unavailable and the feed answered ${res.status}`);
const doc = new DOMParser().parseFromString(await res.text(), 'text/xml');
const text = (item, tag) => item.querySelector(tag)?.textContent ?? '';
return {
totalPages: null,
posts: [...doc.querySelectorAll('item')].map((item) => ({
title: text(item, 'title'),
link: text(item, 'link'),
date: new Date(text(item, 'pubDate')),
})),
};
}
console.log(await wordpressPosts('https://wordpress.org/news'));We tested wordpressPosts() against 4 sites: wordpress.org/news used REST and reported 112 pages of 10 posts, techcrunch.com used REST, css-tricks.com got a 401 from REST and fell back to the feed, and whitehouse.gov threw an error because it had neither REST nor a feed. For more on parsing the XML response or turning it into JSON, see fetching and parsing RSS feeds in JavaScript and our rss2json alternative guide.
Paging, search and feed size
Paging works differently depending on which endpoint succeeded. On the WordPress REST API, you control page size with per_page from 1 to 100 (passing per_page=101 returned 400 rest_invalid_param). WordPress returns the total post count in X-WP-Total (wordpress.org/news reported X-WP-Total: 1114), the page count in X-WP-TotalPages, and next and previous URLs in the Link header, and it lists all three in Access-Control-Expose-Headers so cross-origin JavaScript can read them.
On RSS feeds, you can't pick the page size; you get whatever the site configured (10, 20, or 25 items in our tests). To load older posts, append ?paged=2 (and higher) to the feed URL, or query /?s=<term>&feed=rss2 to search.
If you ever route REST requests or large feeds through https://proxy.cors.dev/, the proxy preserves upstream status codes and also exposes Link, X-WP-Total, and X-WP-TotalPages. The largest feed in our tests (/feed/?paged=2 on wordpress.org/news) was 302,869 bytes, well under the free tier's 6 MiB (6,291,456 bytes) response limit.
Good questions.
What happens if a WordPress site disables pretty permalinks?
Both feeds and the REST API still work through query string URLs. Use /?feed=rss2 instead of /feed/, and use /?rest_route=/wp/v2/posts instead of /wp-json/wp/v2/posts. On wordpress.org/news, /?feed=rss2 returned 10 items in 87,245 bytes and /?rest_route=/wp/v2/posts returned 200.
Why does my date parsing show the wrong time for REST API posts?
WordPress formats the date_gmt field in UTC but leaves off the trailing Z time zone designator. Without that suffix, new Date() in JavaScript treats the timestamp as local browser time instead of UTC. Append "Z" to the date_gmt string before passing it to new Date().
What are the limits on the cors.dev free tier?
The free tier requires no signup and no key, supports GET and HEAD requests only, and doesn't cache responses. It allows about 600 requests a minute per IP (answering 429 with a Retry-After header past that) with no daily or monthly quota, and each upstream request has a 30-second deadline. Responses up to 6 MiB (6,291,456 bytes) are supported, and larger ones fail with HTTP 502 and X-Cors-Error: response_too_large.
Can I request more than 100 posts at once from the WordPress REST API?
No, per_page accepts values from 1 to 100. When we requested per_page=101, WordPress returned HTTP 400 with the error code rest_invalid_param. To fetch more than 100 posts, step through the pages using the page parameter and the X-WP-TotalPages response header.
How do I get the comment feed for a single WordPress post?
Append feed/ to the post's URL, for example https://wordpress.org/news/2026/10/wordpress-7-1-4/feed/. For a site-wide feed of the latest comments across all posts, request /comments/feed/ under the site's root instead. On wordpress.org/news, /comments/feed/ returned 10 items (9,408 bytes), while a single post's comment feed with 0 items was 833 bytes.