You've probably searched for a YouTube thumbnail downloader at some point. Maybe you wanted to study a competitor's design, build a research collection, or just save a thumbnail of your own video before re-uploading. The first 10 results in Google are all the same: a site with a paste-URL input, a "Generate" button, and 14 third-party scripts that load before the download link appears.
Here's the thing: YouTube serves the same five thumbnail files at predictable URLs for every public video. No auth, no API key, no rate limit. The URL pattern hasn't changed since 2014.
The downloader sites are wrappers around img.youtube.com. You can skip them entirely.
The URL pattern
https://img.youtube.com/vi/<VIDEO_ID>/<QUALITY>.jpg
<VIDEO_ID> is the 11-character ID that appears in every YouTube URL. <QUALITY> is one of these:
| Filename | Dimensions | Always available? |
|---|---|---|
maxresdefault.jpg |
1280×720 | Only if creator uploaded a custom HD thumbnail |
sddefault.jpg |
640×480 | Yes |
hqdefault.jpg |
480×360 | Yes |
mqdefault.jpg |
320×180 | Yes |
default.jpg |
120×90 | Yes (used in playlist UI) |
Try it now. Paste this in your browser bar (it's PSY's "Gangnam Style" — definitely public):
https://img.youtube.com/vi/9bZkp7q19f0/maxresdefault.jpg
There's the thumbnail. Right-click, Save Image As, done.
Extracting the video ID from any URL
YouTube has at least 7 URL formats — standard watch, share link, Shorts, embed, music, mobile, old player. The 11-character ID lives in a different position depending on format. One regex catches them all:
const extractYouTubeId = (url) => {
const match = url.match(/(?:v=|youtu\.be\/|shorts\/|embed\/|\/v\/)([\w-]{11})/);
return match?.[1];
};
// Test cases
extractYouTubeId('https://youtube.com/watch?v=9bZkp7q19f0'); // → '9bZkp7q19f0'
extractYouTubeId('https://youtu.be/9bZkp7q19f0?t=42'); // → '9bZkp7q19f0'
extractYouTubeId('https://youtube.com/shorts/abc123xyz4_'); // → 'abc123xyz4_'
extractYouTubeId('https://youtube.com/embed/9bZkp7q19f0'); // → '9bZkp7q19f0'
extractYouTubeId('https://music.youtube.com/watch?v=9bZkp7q19f0'); // → '9bZkp7q19f0'
extractYouTubeId('https://m.youtube.com/watch?v=9bZkp7q19f0'); // → '9bZkp7q19f0'
The character class [\w-]{11} matches exactly 11 characters of word-chars or hyphens (YouTube IDs use letters, numbers, hyphen, underscore — and are always 11 chars long).
The 10-line bookmarklet
Drop this in your browser bookmarks bar. Click it on any YouTube watch page and the maxresdefault thumbnail opens in a new tab:
javascript:(function() {
const m = location.href.match(/(?:v=|youtu\.be\/|shorts\/|embed\/)([\w-]{11})/);
if (m) {
window.open(`https://img.youtube.com/vi/${m[1]}/maxresdefault.jpg`, '_blank');
} else {
alert('Not a YouTube video URL');
}
})();
To install: create a new bookmark, name it "YT Thumb", paste the entire javascript:... string above as the URL. Click it on any YouTube page.
The 404 gotcha nobody mentions
About 40% of YouTube videos return a 404 at /maxresdefault.jpg. This trips up every naive downloader.
The HD thumbnail file only exists when the creator uploaded a custom thumbnail at 1280×720 or larger. Auto-generated thumbnails (the three frame previews YouTube picks when you don't upload one) and pre-2014 uploads don't have a maxres file.
The fix: check sizes in priority order and use the first one that returns 200.
const sizes = ['maxresdefault', 'sddefault', 'hqdefault', 'mqdefault', 'default'];
async function getBestThumbnail(videoId) {
for (const size of sizes) {
const url = `https://img.youtube.com/vi/${videoId}/${size}.jpg`;
const res = await fetch(url, { method: 'HEAD' });
if (res.ok) return { url, size };
}
return null;
}
Note the method: 'HEAD' — you only care about the status code, not the file body. Saves bandwidth and latency.
CORS, or why you can't fetch the image client-side
If you try this in a browser script:
fetch(`https://img.youtube.com/vi/${id}/maxresdefault.jpg`)
.then(r => r.blob())
.then(blob => /* download blob */);
…you'll get a CORS error. img.youtube.com doesn't set Access-Control-Allow-Origin, so the response body is hidden from JavaScript. The HEAD check above works because you're only checking status; the image itself can't be read into a Blob.
Three workarounds:
-
window.open(url)— opens the image in a new tab, user right-clicks to save. No CORS issue because the browser is fetching for display, not for JS. -
<a download>link — sethrefto the image URL, click programmatically. Same idea: browser handles the download, JS doesn't touch the bytes. - Server proxy — your backend fetches the image (no CORS at server-to-server), returns it to your frontend with proper CORS headers. Required if you want to repackage as ZIP or apply edits.
YouTube Shorts
Shorts use the same backend as regular videos. The thumbnail file pattern is identical — only the URL format changes (youtube.com/shorts/<ID> instead of ?v=<ID>). The regex above already handles this case.
One thing to know: even though Shorts display in portrait (9:16), the file YouTube serves is still landscape 16:9 with letterboxing or padding. If you want the actual portrait frame, you need the YouTube Data API.
Show DEV: my hosted version
I built Pixellize YouTube Thumbnail Downloader for the times when "paste a URL, click download" is faster than touching a bookmark. It runs the fallback chain automatically, parses every YouTube URL format the regex above catches, shows you what's available before you commit to a download, and has zero analytics scripts in the page. No signup, no daily limit, no watermark.
I built it because the existing free downloaders all violate one of these:
- Multiple ads above the download link
- Aggressive newsletter popups
- Account required for "premium" sizes
- Mobile redirects to apps
Three rules I wrote it under: don't load third-party scripts (zero tracking pixels), don't gate any quality behind a signup, render the result faster than the ads on competitor sites would have loaded.
Source code for the bookmarklet above + the fallback function are MIT licensed; you can wire them into your own tool. The hosted version handles the edge cases (CORS workaround via server fetch, age-restricted video detection, deleted-video graceful failure) so users get a working result without troubleshooting.
What I learned
The simplest URL pattern beats every "tool" abstraction. Knowing
img.youtube.com/vi/<ID>/<QUALITY>.jpgmakes 90% of "free YouTube thumbnail downloader" sites obsolete for developers.CORS is the only meaningful ceiling. Once you accept that the browser can't read the image bytes directly, you stop fighting it and use
window.openor<a download>.The 40% maxresdefault gap is real. If you're building a tool, the fallback chain is the difference between "works for most videos" and "works for every public video".
A 10-line bookmarklet replaces a 200-script download site. Sometimes the right answer is a one-liner pasted in your bookmark bar, not a SaaS subscription.
If you want the hosted version, here it is: pixellize.io/youtube-thumbnail-downloader. If you'd rather build your own, the code above is everything you need.
Happy to answer questions about the implementation in the comments.

Top comments (0)