Marketplaces reject product photos far more often for file size than for dimensions, and almost nobody documents the ceiling next to the pixel size. I compiled the published upload rules for 18 marketplaces into one file, and the volume constraints turned out to be the most hostile part of it.
Here is what those numbers actually look like, and a way to stop guessing which quality setting fits.
Only 13 of 51 rules publish a cap — and they disagree by 640×
Across the 18 platforms I checked, there are 51 separate rules (main image, zoom, detail, banner, video cover…). 38 of them state no file-size ceiling at all. The 13 that do use 7 different numbers:
| Platform | Slot | Pixels | Ceiling |
|---|---|---|---|
| Pinduoduo | order thumbnail | 200×200 | 32 KB |
| Pinduoduo | home banner (white bg) | 750×352 | 100 KB |
| WeChat Store | main | 600×600 | 1024 KB |
| Pinduoduo | main | 800×800 | 2048 KB |
| Kuaishou | main (white bg) | 800×800 | 2048 KB |
| Temu | carousel (white bg) | 800×800 | 2048 KB |
| Shopee | main | 800×800 | 2048 KB |
| Pinduoduo | SKU | 480×480 | 3072 KB |
| Douyin | main | 600×600 | 5120 KB |
| Douyin | main (white bg) | 800×800 | 5120 KB |
| Douyin | detail | 750×1000 | 5120 KB |
| Shopify | main | 2048×2048 | 20480 KB |
| Shopify | main (max) | 5000×5000 | 20480 KB |
32 KB → 20480 KB is a 640× spread. The photo that sails into Shopify as a 5000×5000 PNG will be bounced by Pinduoduo's 200×200 thumbnail slot unless it is under 32 KB.
Two combinations are genuinely nasty:
- Pinduoduo's 750×352 home banner: white background and ≤ 100 KB. A wide white-background JPEG that small is a compression exercise, not a crop.
- The 200×200 thumbnail at 32 KB: I measured this one instead of guessing, and it is the opposite of what I expected — see the numbers below. The cap is not tight for simple images; it is tight for busy ones, and a real product photo is busy.
I measured it instead of guessing
I took a 3000×2250 test image, resized it to the constrained sizes above, and encoded it as JPEG at six quality settings in Chrome:
| Output size | q .95 | q .90 | q .80 | q .70 | q .50 | q .30 |
|---|---|---|---|---|---|---|
| 200×200 | 8.4 KB | 5.9 KB | 4.3 KB | 3.8 KB | 3.0 KB | 2.4 KB |
| 750×352 | 29.0 KB | 18.4 KB | 12.8 KB | 10.4 KB | 6.6 KB | 5.4 KB |
| 800×800 | 56.7 KB | 33.6 KB | 20.9 KB | 15.5 KB | 11.7 KB | 9.5 KB |
| 750×1000 | 64.3 KB | 38.5 KB | 24.0 KB | 18.2 KB | 13.5 KB | 11.0 KB |
Read the caveat before you reuse these numbers: my test image is synthetic colour gradients, which compress much better than a photograph. Fabric weave, wood grain, food texture, printed text — the things product photos are made of — cost several times more bytes at the same size and quality. So treat this table as a floor, not an estimate for your catalogue.
That reframes the 32 KB slot honestly: at 200×200 even quality 0.95 produced 8.4 KB here, so the ceiling is not absurd on its own. What makes it dangerous is that the same quality setting on a real, textured photo can be an order of magnitude larger — and you only find out at upload time.
Why "export at quality 80" doesn't solve this
File size is roughly pixels × bits-per-pixel. Resizing changes the first term by the square; quality changes the second by 3–6× across the whole usable range (measured above: 56.7 KB → 9.5 KB from q .95 to q .30 at 800×800). So:
- Going 4000×3000 → 800×800 cuts pixel count by ~19×. That is the big lever.
- Going quality 0.95 → 0.50 saves roughly 3–5×, and starts to be visible on product edges and fine text.
If you pick one quality for everything, you are choosing the second lever only, and you will either ship 3 MB files that fail the 100 KB slot, or crush every image to satisfy the single tightest ceiling you happen to have.
What actually fits: downscale first, then search the quality
The honest algorithm is two steps, and the second one is a search, not a guess:
- Resize so the image is exactly the required pixel size (never rely on the file size to imply dimensions).
- If a ceiling exists, binary-search the encoder quality until the encoded bytes fit.
That is what my own pipeline does, and it is not clever — it is just measurement. The search runs between quality 0.30 and 0.95 with at most 8 encode attempts, taking the highest quality that fits:
// canvas -> Blob, then binary search on quality for a byte budget
async function encodeWithBudget(encode, maxBytes) {
let lo = 0.30, hi = 0.95, best = null;
for (let i = 0; i < 8; i++) {
const q = (lo + hi) / 2;
const blob = await encode(q); // encode(q) -> Promise<Blob>
if (blob.size <= maxBytes) { best = { blob, q }; lo = q; }
else { hi = q; }
if (hi - lo < 0.02) break;
}
return best; // null => even q=0.30 doesn't fit
}
Two properties matter more than the exact numbers:
-
It returns
nullinstead of lying. If quality 0.30 still exceeds the ceiling, you must shrink the image or change the format — silently shipping an oversized file just moves the rejection downstream. - It never re-encodes more than ~8 times per image. With 51 outputs per photo you can feel an unbounded search loop on a phone.
For reference, an audit run of 15 outputs from my tool (200×200 up to 1000×2000, white-background and transparent slots mixed) produced JPEGs between 15 KB and 44 KB. That is under every ceiling in the table except the 32 KB thumbnail — and 32 KB sits inside that range, so whether an output passes depends on the image, which is the whole point.
A five-second check on your own folder
Before blaming the platform, see which files are over which ceiling. macOS / Linux:
find ./exports -iname '*.jpg' -size +2048k -exec ls -lh {} \;
Windows PowerShell:
Get-ChildItem .\exports\*.jpg | Where-Object Length -gt 2MB | Select-Object Name, Length
And if you want to know what a given pixel size can reach at all, measure the encoder instead of trusting a blog post:
// returns the smallest quality that fits, or null
const probe = async (img, w, h, maxBytes) => {
const c = new OffscreenCanvas(w, h);
c.getContext('2d').drawImage(img, 0, 0, w, h);
return encodeWithBudget(async (q) => c.convertToBlob({ type: 'image/jpeg', quality: q }), maxBytes);
};
The part nobody documents
The 38 rules without a published ceiling are not permission to upload 40 MB. In practice they mean "the platform will re-encode your file and may reject it if it is absurd". I kept them as maxKB: null in the dataset rather than inventing a number, because a made-up limit is worse than a missing one.
Same for the white-background slots: 16 of the 51 rules require a white background, and "white" means the pixels, not the intent. In my own test images the "white" sweep of a photo-style shot measured 246–252 on all three channels — visibly white on screen, and not white to a checker that wants 255. If your pipeline only fills the letterbox padding with white, you will fail these slots even though the image "looks white".
Where the data lives
The full set — 18 platforms, 51 rules, a maxKB (or explicit null) and a sourceUrl on every rule:
- JSON:
https://tugui.top/data/specs.json - CSV:
https://tugui.top/data/specs.csv - npm:
npm install tugui-specs - mirror: github.com/pgodace/tugui-specs-data, MIT
25 of the 51 rules trace to the platform's own documentation, 26 only to third-party write-ups — each rule says which. If a ceiling has changed, send me the URL and I will fix it.
I also build the tool that produced the measurements in this post: it resizes one photo into every marketplace size in the browser, nothing uploaded (tugui.top). Five files per batch free, $4/month or $12/year. The dataset is useful without it.
Top comments (0)