GuidesFEEDS
YouTube RSS feed URL for any channel or playlist
Every YouTube channel has a public Atom feed at https://www.youtube.com/feeds/videos.xml?channel_id=CHANNEL_ID that lists the channel's 15 most recent uploads with no API key and no account needed. Playlists use the same URL with playlist_id=PLAYLIST_ID. YouTube sends no Access-Control-Allow-Origin header on the feed, so browser JavaScript reads it through https://proxy.cors.dev/, which is free and needs no signup.
Feed URL formats
The base URL https://www.youtube.com/feeds/videos.xml takes one query parameter: channel_id, playlist_id or the older user. Channel IDs start with UC. A channel's uploads are also available as playlists: replace the UC at the start of the channel ID with UU for all uploads, UULF for uploads without Shorts, or UUSH for Shorts only. The table below uses the Google for Developers channel, UC_x5XG1OV2P6uZZ5FSM9Ttw.
| Feed | URL | Entries |
|---|---|---|
| Channel uploads | https://www.youtube.com/feeds/videos.xml?channel_id=UC_x5XG1OV2P6uZZ5FSM9Ttw | 15 latest, Shorts included |
| All uploads (UU) | https://www.youtube.com/feeds/videos.xml?playlist_id=UU_x5XG1OV2P6uZZ5FSM9Ttw | 15 latest, Shorts included |
| Uploads without Shorts (UULF) | https://www.youtube.com/feeds/videos.xml?playlist_id=UULF_x5XG1OV2P6uZZ5FSM9Ttw | 15 latest, no Shorts |
| Shorts only (UUSH) | https://www.youtube.com/feeds/videos.xml?playlist_id=UUSH_x5XG1OV2P6uZZ5FSM9Ttw | 15 latest Shorts |
| Playlist | https://www.youtube.com/feeds/videos.xml?playlist_id=PLOU2XLYxmsILZpGc5gWRO1vL0n9CtcPJL | Up to 15 |
| Legacy username | https://www.youtube.com/feeds/videos.xml?user=GoogleDevelopers | 15 latest, channels with a username |
| @handle | https://www.youtube.com/feeds/videos.xml?handle=@GoogleDevelopers | Not supported: HTTP 400 |
The feed does not accept an @handle: ?handle= returns HTTP 400. Use the channel ID instead; the next section shows where to find it.
Find the channel ID
- Open the channel page
Open the channel on YouTube, for example
https://www.youtube.com/@GoogleDevelopers. - Open the About panel
Click the "...more" link after the channel description to open the About panel.
- Copy the channel ID
Scroll to "Share channel" and choose "Copy channel ID". The ID starts with
UC; paste it afterchannel_id=in the feed URL.
Another way to find the ID is to view the channel page's source and search for channel_id=. The page contains a <link rel="alternate" type="application/rss+xml"> tag with the full feed URL. Your code cannot read the channel page through the free proxy: in our test the page HTML was about 1.6 MB, above the 1 MiB response cap, and the proxy answered 502 with X-Cors-Error: response_too_large. Look the ID up once and keep it in your code or config.
Fetch the feed in JavaScript
A direct fetch() of the feed from a web page fails because YouTube's feed response has no Access-Control-Allow-Origin header. Chrome logs No 'Access-Control-Allow-Origin' header is present on the requested resource and fetch() rejects with TypeError: Failed to fetch. Prefix the feed URL with https://proxy.cors.dev/: the proxy fetches the feed and returns it with Access-Control-Allow-Origin set to your page's origin. The free tier needs no signup and no API key, and accepts GET requests with XML responses up to 1 MiB, while the feed is about 25 KB.
const channelId = 'UC_x5XG1OV2P6uZZ5FSM9Ttw';
const feedUrl = `https://www.youtube.com/feeds/videos.xml?channel_id=${channelId}`;
const response = await fetch(`https://proxy.cors.dev/${feedUrl}`, { credentials: 'omit' });
if (!response.ok) {
throw new Error(`Feed request failed: ${response.headers.get('X-Cors-Error') ?? response.status}`);
}
const feed = new DOMParser().parseFromString(await response.text(), 'application/xml');
if (feed.querySelector('parsererror')) throw new Error('The feed is not valid XML');
const YT = 'http://www.youtube.com/xml/schemas/2015';
const MEDIA = 'http://search.yahoo.com/mrss/';
const videos = [...feed.getElementsByTagName('entry')].map((entry) => ({
id: entry.getElementsByTagNameNS(YT, 'videoId')[0].textContent,
title: entry.getElementsByTagName('title')[0].textContent,
url: entry.querySelector('link[rel="alternate"]').getAttribute('href'),
published: new Date(entry.getElementsByTagName('published')[0].textContent),
thumbnail: entry.getElementsByTagNameNS(MEDIA, 'thumbnail')[0].getAttribute('url'),
views: Number(entry.getElementsByTagNameNS(MEDIA, 'statistics')[0]?.getAttribute('views') ?? 0),
}));
console.log(videos.length, videos[0]);The code parses the XML with DOMParser and turns each entry into an object with the video ID, title, watch URL, publish date, thumbnail URL and view count. yt:videoId, media:thumbnail and media:statistics use XML namespaces, so the code reads them with getElementsByTagNameNS(). When a request fails, the proxy puts an error code in the X-Cors-Error response header, and the code adds it to the error message.
// Markup: <ul id="videos"></ul>
const list = document.querySelector('#videos');
list.replaceChildren(
...videos.slice(0, 6).map((video) => {
const item = document.createElement('li');
const link = document.createElement('a');
const thumbnail = document.createElement('img');
link.href = video.url;
thumbnail.src = video.thumbnail;
thumbnail.alt = '';
thumbnail.width = 480;
thumbnail.height = 360;
thumbnail.loading = 'lazy';
link.append(thumbnail, video.title);
item.append(link);
return item;
}),
);The render code creates elements with DOM methods and adds titles as text, so a video title cannot inject HTML. Each thumbnail is the hqdefault.jpg image at 480 by 360 pixels. The code sets width and height to reserve the space and loads images lazily.
Fields in each entry
Each entry element holds one video. The feed itself also carries the channel name in title and a link to the channel page. The table lists the entry fields most pages need.
| Element | Namespace | Value |
|---|---|---|
| yt:videoId | http://www.youtube.com/xml/schemas/2015 | Video ID |
| title | Atom | Video title |
| link rel="alternate" | Atom | Watch URL, or a /shorts/ URL for Shorts |
| published | Atom | Upload time with UTC offset |
| media:thumbnail | http://search.yahoo.com/mrss/ | hqdefault.jpg URL, 480 by 360 |
| media:description | http://search.yahoo.com/mrss/ | Video description |
| media:statistics | http://search.yahoo.com/mrss/ | View count in the views attribute |
Limits
- The feed returns only the 15 most recent entries and has no paging; older videos need the YouTube Data API, which requires an API key.
- YouTube serves the feed with
Cache-Control: public, max-age=900, which lets caches reuse a copy for 15 minutes, so polling more often than every 15 minutes gains little. - The proxy does not cache feeds (
Cache-Control: no-store) and applies fair-use rate limits per IP; each visitor's browser sends its own requests, so the limit applies per visitor. - Shorts appear in the channel feed with links like
https://www.youtube.com/shorts/VIDEO_ID; use theUULFplaylist to leave them out.
Good questions.
What is the RSS feed URL for a YouTube channel?
The URL is https://www.youtube.com/feeds/videos.xml?channel_id=CHANNEL_ID. The channel ID starts with UC. The feed is Atom XML with the 15 latest uploads.
How do I get a YouTube RSS feed without Shorts?
Use playlist_id= with the channel ID's UC prefix replaced by UULF. UUSH gives Shorts only, while the plain channel feed includes Shorts.
Does the YouTube RSS feed need an API key?
No, the feed is public and needs no API key or account. The YouTube Data API needs a key, and it is the option for more than the latest 15 videos.
How many videos does a YouTube RSS feed show?
Channel feeds show the 15 most recent uploads, and playlist feeds show up to 15 entries. There is no paging parameter.
Why does fetch() fail when I load a YouTube RSS feed?
YouTube does not send Access-Control-Allow-Origin for the feed, so the browser blocks your page from reading it. Server-side code is not affected. In browser code, prefix the feed URL with https://proxy.cors.dev/.
Can I use a channel's @handle in the feed URL?
No, ?handle= returns HTTP 400. The feed needs the channel ID from the channel's About panel ("Share channel", then "Copy channel ID").