
A deep dive into Pinterest’s image CDN reveals a clean URL pattern, size‑based folders, and sharded hashes. Learn how to upgrade thumbnails to originals, handle videos, and reliably extract media from pin pages.
Understanding the Pinimg URL Structure
A Pinterest image URL follows a deterministic pattern that makes it possible to locate any size variant of the same asset without additional metadata. The general form is:
https://i.pinimg.com/{size}/{shard1}/{shard2}/{shard3}/{hash}.ext
Host – i.pinimg.com is the image CDN endpoint (video assets use v1.pinimg.com). All image traffic is routed through this host.
Size folder – The first path segment after the host specifies the requested rendition. It is a width‑based token such as 75x75_RS, 236x, 1200x, or the literal originals. The token encodes the target width; a missing height (e.g., 236x) means “scale to 236 px wide, preserve aspect ratio”.
75x75_RS– square avatar‑style crop140x,170x– tiny previews236x– home‑feed grid474x,564x– larger board views736x– pin close‑up page1200x– large displayoriginals– the un‑resized upload
Shard folders – The next three segments are derived from the first six characters of the content hash, split into pairs (e.g., 3f/8a/c1). This sharding distributes files across directories, preventing any single folder from containing millions of entries.
Filename – The final component combines the full content hash with the original file extension (e.g., 3f8ac1d2e9b4f7a6c5d2e1b9a8f7c6d.jpg). The hash is calculated from the binary content of the uploaded file and remains identical for every size variant; only the size folder changes.
Because the hash does not vary, upgrading a thumbnail to a higher‑resolution version is a simple string replacement. A concise regex used in many extraction tools is:
const SIZE_RE = /\/(?:\d+x(?:_\w+)?|originals)\//;
function toSize(url, size = 'originals') {
return url.replace(SIZE_RE, `/${size}/`);
}
Applying toSize to https://i.pinimg.com/236x/3f/8a/c1/3f8ac1d2.jpg yields https://i.pinimg.com/originals/3f/8a/c1/3f8ac1d2.jpg, demonstrating that the underlying hash and shard path stay constant while the size token drives the rendition selection.
Size Folders and Upgrading to Original Files
Each image on the CDN lives under a size‑specific folder that encodes the desired width while preserving the original aspect ratio. The folder name is the only part that changes between variants; the hash‑based shard path (e.g., 3f/8a/c1) and the filename remain constant.
- 75x75_RS – square avatar‑style crop (75 px width, height forced to 75 px).
- 140x, 170x – tiny previews used in list views.
- 236x – width for the home‑feed grid.
- 474x, 564x – larger grid or board‑view thumbnails.
- 736x – close‑up page display.
- 1200x – high‑resolution display for detailed inspection.
- originals – the unmodified file uploaded by the creator; the extension may differ from the thumbnail’s (
.jpg,.png,.webp).
Upgrading a thumbnail to another size is a single regular‑expression replacement. The pattern matches any size folder or the originals token:
const SIZE_RE = /\/(?:\d+x\d*(?:_RS)?|originals)\//;
function toSize(url, size = 'originals') {
return url.replace(SIZE_RE, `/${size}/`);
}
Example:
toSize('https://i.pinimg.com/236x/3f/8a/c1/3f8ac1d2.jpg');
// → https://i.pinimg.com/originals/3f/8a/c1/3f8ac1d2.jpg
Because originals may be missing or stored with a different extension, a ladder‑fallback strategy is recommended. The client attempts each size in order, issuing a HEAD request to verify availability before returning the first successful URL.
const LADDER = ['originals', '1200x', '736x', '564x'];
async function bestAvailable(baseUrl) {
for (const size of LADDER) {
const candidate = toSize(baseUrl, size);
const res = await fetch(candidate, {method: 'HEAD'});
if (res.ok) return candidate;
}
return baseUrl; // fall back to the original thumbnail
}
If a HEAD request returns 403 or 404, the loop can optionally retry with alternate extensions (e.g., replace .jpg with .png or .webp) before descending the ladder. This approach guarantees the highest‑quality asset reachable without hard‑coding paths, making it robust against CDN layout changes and differing upload formats.
Server‑Side Fetching and CORS Considerations
When a thumbnail URL contains a size prefix such as 236x, the hash part of the path identifies the original file, while the size folder determines which resized variant is served. Because the hash never changes, upgrading an image is a matter of swapping the size segment for a larger one (e.g., originals, 1200x, 736x, etc.). However, browsers enforce the Same‑Origin Policy: a request for https://i.pinimg.com/… from a page on a different origin will be blocked unless the CDN sends an Access‑Control‑Allow‑Origin header that matches the caller. Pinterest’s CDN does not provide such CORS headers, so a client‑side fetch that follows the size ladder will fail with a CORS error before the response can be inspected.
Running the ladder logic on the server eliminates the browser’s CORS barrier, because the server can make direct HTTP requests to the CDN, examine the status code, and return the chosen URL to the client. This approach also respects security standards such as OWASP recommendations for server‑side validation of external resources.
- Why server‑side is required
- Browsers block cross‑origin
HEADrequests without CORS headers. - Server code can safely follow redirects and handle authentication tokens if needed.
- Rate‑limiting and caching can be applied centrally to reduce load on the CDN.
- Browsers block cross‑origin
- Size‑ladder algorithm
- Define an ordered list of candidate sizes, from highest quality to the thumbnail you already have.
- For each size, construct a candidate URL by replacing the size segment.
- Issue a
HEADrequest; if the response is200 OK, return that URL. - If none succeed, fall back to the original URL.
const SIZE_RE = /\/(?:\d+x\d*_RS|originals)\//;
function toSize(url, size) {
return url.replace(SIZE_RE, `/${size}/`);
}
const LADDER = ['originals', '1200x', '736x', '564x'];
async function bestAvailable(url) {
for (const size of LADDER) {
const candidate = toSize(url, size);
const res = await fetch(candidate, { method: 'HEAD' });
if (res.ok) return candidate;
}
return url; // no larger variant found
}
Deploy this function in a backend service (e.g., a serverless endpoint) and expose only the final URL to the client. The client receives a CORS‑safe link, while the heavy lifting—size negotiation and HEAD probing—remains safely behind the server’s trusted environment.
Video Assets: Separate Host and Retrieval Logic
All video files are served from a dedicated host v1.pinimg.com, which isolates streaming assets from the image CDN (i.pinimg.com). The host follows a predictable path structure that mirrors the image sharding scheme: the first six characters of the content hash are split into three two‑character folders, then the hash itself appears in the filename.
- Adaptive HLS playlist:
https://v1.pinimg.com/videos/mc/hls/ab/cd/ef/<hash>.m3u8– an HLS manifest that references segmented.tsfiles and lets the player switch quality on the fly. - Single‑file MP4:
https://v1.pinimg.com/videos/mc/720p/ab/cd/ef/<hash>.mp4– a progressive MP4 suitable for download or archival.
Because the HLS variant is a playlist rather than a direct file, most backend workflows prefer the MP4 version. The pin’s JSON payload contains a video_list object whose keys (e.g., V_720P, V_HLSV4) map to metadata objects that include url, width, and height. Selecting the highest‑resolution MP4 requires two steps: filter out non‑MP4 entries, then sort by pixel count.
function pickBestMp4(videoList) {
return Object.values(videoList)
.filter(v => v.url?.endsWith('.mp4'))
.sort((a, b) => (b.width * b.height) - (a.width * a.height))[0] ?? null;
}
Example usage:
// Assume data is the parsed hydration JSON from a pin page
const bestVideo = pickBestMp4(data.video_list);
if (bestVideo) {
console.log('Download URL:', bestVideo.url);
}
When building a resolver, the typical flow is:
- Fetch the pin page and locate the
__PWS_DATA__script. - Parse the script to obtain the
video_listobject. - Apply
pickBestMp4to retrieve the largest MP4 URL. - Optionally fall back to the HLS manifest if no MP4 entry exists.
Note that the retrieved URL reflects the original upload; a 720p source cannot be magically upgraded to 1080p. The logic above respects the platform’s CDN design while providing a deterministic, maintainable way to extract the best available video asset.
Three‑Tier Media Extraction from Pin Pages
The media for a Pinterest pin is embedded in three distinct layers of the page markup. Each layer offers a different trade‑off between completeness and stability, so a robust extractor should query them in order of richness while falling back to the next tier when a lookup fails.
- Tier 1 – Hydration JSON (
__PWS_DATA__script) – contains the full Redux state withimages.orig.urlfor images and avideo_listobject for videos. - Tier 2 – JSON‑LD (
application/ld+jsonblocks) – provides a structuredimageorcontentUrlfor video, but may appear multiple times and not all blocks relate to the pin. - Tier 3 – OpenGraph (
og:image/og:videometa tags) – guarantees at least one media URL, usually a mid‑size image, and is the most stable across front‑end releases.
Below are minimal extraction functions that illustrate the hierarchy. The functions return the first non‑null result, preserving the “richest‑first” strategy.
function extractMedia(html) {
return fromHydration(html) ?? fromJsonLd(html) ?? fromOpenGraph(html);
}
OpenGraph fallback – a simple regex grabs the og:image value and then upgrades it to the desired size using the CDN size‑folder pattern.
function fromOpenGraph(html) {
const m = html.match(/<meta[^>]+property="og:image"[^>]+content="([^"]+)"/);
return m ? {type: 'image', url: toSize(m[1])} : null;
}
JSON‑LD parser – iterates over every <script type="application/ld+json"> block, parses the JSON, and extracts either contentUrl (video) or image.url (image).
function fromJsonLd(html) {
const blocks = html.matchAll(/<script[^>]+type="application\/ld\+json">([\s\S]*?)<\/script>/g);
for (const [, raw] of blocks) {
try {
const data = JSON.parse(raw);
if (data.contentUrl) return {type: 'video', url: data.contentUrl};
const img = data.image?.url ?? data.image;
if (typeof img === 'string') return {type: 'image', url: toSize(img)};
} catch {}
}
return null;
}
Hydration JSON – rather than hard‑coding a path such as props.initialReduxState.pins[id], the extractor searches the parsed object for a shape that matches the expected media structure. This “search‑by‑shape” approach survives arbitrary reshuffling of the Redux tree.
function findFirst(node, predicate) {
if (!node || typeof node !== 'object') return null;
if (predicate(node)) return node;
for (const v of Object.values(node)) {
const hit = findFirst(v, predicate);
if (hit) return hit;
}
return null;
}
function fromHydration(html) {
const m = html.match(/<script[^>]+id="__PWS_DATA__"[^>]*>([\s\S]*?)<\/script>/);
if (!m) return null;
const data = JSON.parse(m[1]);
const video = findFirst(data, n => n.video_list);
if (video) return {type: 'video', url: pickBestMp4(video.video_list).url};
const pin = findFirst(data, n => n.images?.orig?.url);
return pin ? {type: 'image', url: pin.images.orig.url} : null;
}
By chaining the three tiers and relying on shape‑based discovery, the extractor remains functional even when Pinterest deploys a new front‑end bundle that renames or relocates fields. The fallback layers ensure that a missing or broken tier does not abort the whole process, providing a resilient foundation for large‑scale media harvesting.
Practical Implementation, Edge Cases, and Ethics
When a Pinterest short‑link (e.g., https://pin.it/abcd) is supplied, the first step is to resolve the HTTP 302 redirect and capture the final URL from response.url. All downstream extraction must use that resolved URL; otherwise the subsequent fetch will hit a redirect loop or a login wall.
Pin media lives on two CDNs: i.pinimg.com for images and v1.pinimg.com for videos. Image URLs contain a size folder (e.g., /236x/) followed by a three‑level hash directory. Replacing the size segment with /originals/ yields the uploaded file, but the original may have a different extension or be unavailable (403/404). A reliable “ladder” checks originals, then descending sizes such as 1200x, 736x, and 564x until a HEAD request succeeds.
Video assets differ: the HLS playlist (.m3u8) streams adaptive bitrate, while the MP4 variant (.mp4) is a single progressive file. Selecting the highest‑resolution MP4 requires iterating the video_list object and sorting by pixel count.
- Story and carousel pins contain multiple media blocks; the extractor must return an array of URLs, one per page or slot.
- Animated GIFs are often served as short MP4 loops; the true
.gifmay only exist underoriginals. Check both extensions before falling back. - Extension mismatches occur when the grid shows
.jpgbut the source is.pngor.webp. Attempt alternate extensions if the primary request fails. - Private or removed pins return only a login prompt; the resolver should abort early with a clear error rather than retry.
Caching strategy: store the resolved media URLs keyed by the pin ID. A single fetch per pin populates the cache; subsequent requests read from memory or a distributed cache (e.g., Redis) without contacting Pinterest again. This reduces latency and respects rate limits.
Rate‑limiting: enforce a modest request ceiling (e.g., one request per second per IP) and exponential back‑off on 429 Too Many Requests. Align limits with Pinterest’s API usage policies and any applicable compliance frameworks such as SOC 2 or ISO 27001, which require controlled access to external services.
Terms of Service compliance: operate only on publicly viewable pins, honor takedown notices, and attribute creators when redistributing media. Automated tools must not bypass authentication or scrape private content, ensuring legal and ethical use of Pinterest’s CDN.
Looking for Custom Software or AI Solutions?
Appworks Technologies designs, builds, and scales production enterprise platforms, microservices, and AI agent workflows tailored to your business goals.
Editorial Policy & Research Methodology
Our findings are based on rigorous internal research, verified industry benchmarks, and direct technical implementation experience from our enterprise client projects. All statistics and technical claims are reviewed by senior engineers before publication to ensure accuracy, transparency, and helpfulness for our readers.
