Dev.to · 7 min read

How image CDNs hide the original: size tokens in URLs on Bluesky, Mercado Libre, Blogger, Steam and Google Play

How image CDNs hide the original: size tokens in URLs on Bluesky, Mercado Libre, Blogger, Steam and Google Play

Most big sites store one upload and serve resized copies of it from a CDN. The size of the copy is written into the image URL: sometimes as a path segment, sometimes as two letters before the extension, sometimes after an = at the end. If you know where that token sits, you can edit the URL in the address bar and get the stored file instead of the thumbnail. I measured five sites this week. Each image below was fetched with curl and its pixel size read from the file header, so these are real numbers from November 2026, not documentation. CDNs change, so check them again before you build anything on them. Site What the page loads Edit What you get Bluesky feed_thumbnail, 1000×750 WebP feed_fullsize 4000×3000 Mercado Libre D_Q_NP_2X_…-AB.webp, 448×448 D_NQ_NP_…-OO.jpg 1704×1919 Blogger (unlinked image) w320-h213-rw, 320×212 WebP s0 566×376 PNG Steam ss_.600x338.jpg, 600×337 drop .600x338 1920×1080 Google Play =w1052-h592-rw, 333×592 WebP =s0 1080×1920 PNG Bluesky: a different path prefix An image in a post loads from an address like this: https://cdn.bsky.app/img/feed_thumbnail/plain/did:plc:z72i7hdynmk6r22z27h6tvur/bafkreih3mb3cwnbc5kv5b2qyy24q6banms25i5ut3cbty4ej2x7vjvd6y4 did:plc:… is the account and bafkrei… is the image blob. Bluesky's public API lists two addresses for every image, thumb and fullsize, and they differ only in that prefix. I tried two posts by @bsky.app: Upload feed_thumbnail feed_fullsize feed_fullsize@jpeg JPEG 4000×1916 1000×479 WebP, 28 KB 4000×1916 WebP, 160 KB 4000×1916 JPEG, 395 KB JPEG 4000×3000 1000×750 WebP, 159 KB 4000×3000 WebP, 1.3 MB 4000×3000 JPEG, 2.0 MB So the thumbnail is capped at 1000 px and the full-size copy has every pixel of the upload, re-encoded. The suffix after @ picks the format: @jpeg, @png or @webp. @png is lossless and large (17.6 MB for the 4000×3000 photo). If you need the uploaded bytes themselves, com.atproto.sync.getBlob on the account's server returns the original JPEG (1.8 MB for the first image). One @jpeg request returned HTTP 400 "Source is unreachable" and worked on the retry, so a script should retry once before it falls back. Mercado Libre: two letters before the extension Listing grids on mercadolivre.com.br load addresses like: https://http2.mlstatic.com/D_Q_NP_2X_902305-MLB88750630332_082025-AB.webp The middle part is the photo ID. The prefix and the suffix choose the copy: D_Q_NP_ pads the photo out to a square. D_NQ_NP_ keeps its real shape. 2X_ doubles the small sizes. The letters before the extension are the size. AB was 448 px on the Brazilian pages, F caps the long side at 1200 px, and OO is the stored size. The extension picks the format. Ask for .jpg and you get a JPEG. D_NQ_NP_902305-MLB88750630332_082025-OO.jpg turned the 448×448 square into 1704×1919, the photo's real shape. On the best-sellers page I tried all 38 photos: OO worked for every one and returned sizes from 751×1193 to 1920×1920. F shrank 25 of the 38, for example a 1920×1920 photo came back as 1200×1200. That matches Mercado Libre's own developer docs, which cap uploads at 1920×1920. 17 of those 38 URLs had the product name after the size letters (…-AB-.webp). The CDN ignores the name, so you can delete it along with the letters. A pattern that expects the URL to end right after AB misses those, which is the bug I had in my own code. Blogger: a size segment before the file name https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiS…/w400-h314-rw/Jump_feed.png The segment before the file name is the size. w400-h314 fits the image in a 400×314 box, s1600 fits it in 1600 px on the long side, and s0 asks for the upload. The -rw part makes Blogger send WebP whatever the file name says. The Blogger editor wraps each image in a link to a large copy by default, and I measured three images on two public Blogspot posts: Image On the page Linked copy s0 Screenshot, linked w400-h314-rw: 400×314 WebP s1187: 1187×931 PNG 1187×931 PNG Photo, linked s900-rw: 800×530 WebP s800: 800×530 JPEG 800×530 JPEG Screenshot, no link w320-h213-rw: 320×212 WebP none 566×376 PNG So when the link is there, it already points at the upload, in the uploaded format. When the writer removed the link, s0 is the only way to the full image. Note the second row: asking for 900 px returned the 800 px upload. Blogger never enlarged an image, which makes s0 safe to use blindly. Steam: a size in the file name A store page (I used Stardew Valley, app 413150, which has no age gate) lists each screenshot ID in its page data with three sizes: 116x65, 600x338 and 1920x1080. The carousel shows the middle one: …steamstatic.com/…/ss_.600x338.jpg -> 600×337 JPEG …steamstatic.com/…/ss_.1920x1080.jpg -> 1920×1080 JPEG, 966 KB …steamstatic.com/…/ss_.jpg -> the same 1920×1080 file Removing the .600x338 token gives the full screenshot. Games with mature content put an age check in front of the store page, so a script fetching the HTML will get that page instead. Google Play: everything after = Play's image server, play-lh.googleusercontent.com, takes size and format options after an = at the end of the path. On the Wikipedia app's Play listing, a screenshot looks like this: The 1x copy was 166×296 WebP and the 2x copy 333×592 WebP (75 KB). Replace the whole tail with =s0 and you get 1080×1920 PNG, 848 KB, which is the size the developer uploaded. =w5000-h5000 returned the same 1080×1920 file, so like Blogger it does not upscale, but =s0 is shorter and says what you mean. The same edits as code If you want to do this in a script, each site is one host test and one replace. This is a simplified version, with a check that the edits do what the sections above say: const RULES = [ [/^cdn\.bsky\.app$/, (u) => { u.pathname = u.pathname.replace('/img/feed_thumbnail/', '/img/feed_fullsize/'); }], [/(^|\.)mlstatic\.com$/, (u) => { u.pathname = u.pathname.replace( /^\/D_(?:(?:Q|NQ|NP|2X)_)*(\d+-[A-Z]+\d+_\d+)-[A-Z]{1,2}(?:-[^/]+?)?\.(webp|jpe?g|png)$/, '/D_NQ_NP_$1-OO.$2'); }], [/^blogger\.googleusercontent\.com$/, (u) => { u.pathname = u.pathname.replace(/\/(?:s\d+|w\d+-h\d+)(?:-[a-z0-9-]+)?\/(?=[^/]+$)/, '/s0/'); }], [/\.steamstatic\.com$/, (u) => { u.pathname = u.pathname.replace(/(\/ss_[0-9a-f]+)\.\d+x\d+(\.jpg)$/, '$1$2'); }], [/^play-lh\.googleusercontent\.com$/, (u) => { u.pathname = u.pathname.replace(/=[^=/]*$/, '=s0'); }], ]; function fullSize(href) { const u = new URL(href); for (const [host, apply] of RULES) { if (host.test(u.hostname)) { apply(u); break; } } return u.href; } console.assert(fullSize('https://http2.mlstatic.com/D_Q_NP_2X_902305-MLB88750630332_082025-AB-calca-legging.webp') === 'https://http2.mlstatic.com/D_NQ_NP_902305-MLB88750630332_082025-OO.webp'); console.assert(fullSize('https://play-lh.googleusercontent.com/abc=w526-h296-rw') === 'https://play-lh.googleusercontent.com/abc=s0'); Two things the snippet leaves out and a real tool needs. First, a fallback: if the rewritten URL fails or returns an HTML error page, save the URL the page showed instead. Second, keep the host tests exact. A rule that fires on the wrong host turns a working URL into a broken one. What my extension does with these I build PixLasso, a Chrome and Edge extension that saves the images on a page into one ZIP at the largest size it can find. Where it stands on these five sites, as of version 0.1.5: Mercado Libre: the released rule rewrites URLs that end right after the size letters. The fix for URLs with the product name is written and tested but ships in the next version. Bluesky: the feed_fullsize rule is also in the next version. 0.1.5 saves the 1000 px copies. Blogger: linked images come out at full size because the extension follows the link around an image. Unlinked ones stay at page size; there is no s0 rule yet. Steam and Google Play: no rules yet, so it saves what the page shows. Until then, the address bar edits above are the way to get the big files. Disclosure: I made PixLasso. It is free up to 30 images per batch, and Pro is $9.99 once. https://pixlasso.com/?src=devto

This is a summary aggregated from Dev.to. Read the complete article on the original site:

Read full article at Dev.to

More Gadgets & Consumer Tech