Where the 10 KB number comes from, and what it skips
The advice you will find repeated is that images under about 10 KB are fine to inline. No page I have read shows the arithmetic behind that figure, and none of them splits it by format or re-runs it after compression. Both omissions change the answer, so here is the derivation with every input written down.
Input 1 — the raw inflation is exact, not approximate
Base64 carries 6 bits per character, so three bytes become four characters. The length is therefore exactly 4 × ⌈bytes ÷ 3⌉, with up to two = for padding. Across the nine files I measured, the inflation landed between +33.33% and +34.13% — the top of that range is the 167-byte SVG, where the two padding characters matter most. Treat +33.33% as the floor and add the padding.
Input 2 — compression gives most of it back, and the amount depends on the format
This is the input the usual advice never mentions. A server gzips the document it sends, Base64 text included, so what matters is the compressed size of the Base64 text, not its length. I gzipped both the file and its Base64 text at level 9, and Brotli at quality 11, for each sample:
| What was inlined | File bytes | Base64 chars | Raw +% | File gzipped | Base64 gzipped | Inlined ÷ file | Brotli ÷ file | Overhead recovered |
|---|---|---|---|---|---|---|---|---|
| PNG, 640 × 360 | 5,879 | 7,840 | +33.36% | 4,652 | 4,914 | 1.056× | 1.055× | 86.6% |
| JPEG, 640 × 360 | 15,084 | 20,112 | +33.33% | 13,262 | 13,147 | 0.991× | 0.995× | 102.3% |
| WebP, 640 × 360 | 9,254 | 12,340 | +33.35% | 8,948 | 8,933 | 0.998× | 1.003× | 100.5% |
| PNG icon, 32 × 32 | 615 | 820 | +33.33% | 638 | 655 | 1.027× | 1.031× | 91.7% |
| PNG, 200 × 200 flat colour | 1,643 | 2,192 | +33.41% | 1,546 | 1,600 | 1.035× | 1.056× | 90.2% |
| PNG, 200 × 200 with alpha | 4,951 | 6,604 | +33.39% | 4,941 | 4,998 | 1.012× | 1.020× | 96.6% |
| Raw RGBA, 200 × 200 | 160,000 | 213,336 | +33.34% | 196 | 471 | 2.403× | 2.556× | 99.5% |
| BMP, 200 × 200 flat colour | 120,054 | 160,072 | +33.33% | 191 | 237 | 1.241× | 0.934× | 99.9% |
| SVG, 200 × 200 (text) | 167 | 224 | +34.13% | 134 | 201 | 1.500× | 1.667× | −17.5% |
Read the last three columns. For an already-compressed raster — JPEG, WebP, PNG — gzip recovers essentially the whole third, so inlining costs between 0.99× and 1.06× of what the file would have cost gzipped. For the SVG it goes the other way: the overhead grew by 17.5% under gzip and 67% under Brotli, because Base64 turns compressible text into something that looks closer to noise. A flat 33% is the wrong number for every one of these nine files.
Two rows look strange and are worth the second look. The 200 × 200 raw RGBA buffer is 160,000 bytes that gzip collapses to 196 — and its Base64 to 471, a ratio of 2.403×. Inlining a compressible binary costs you a multiple of the file, not a third more. And the 32 × 32 icon is 615 bytes that gzip makes larger (638), because gzip's own header outweighs any gain on something that small. Small compressed files do not benefit from gzip at all.
Input 3 — the same file in more than one document
An inlined asset travels inside every document that contains it. A file served separately is fetched once and then cached. So with the asset in K documents, inlining costs K × gzipped(Base64) while the separate file costs gzipped(file) once. Inlining wins on bytes only while K is below gzipped(file) ÷ gzipped(Base64). Across my nine samples that break-even came out at 0.947, 1.009, 1.002, 0.974, 0.966, 0.988 and 0.667 — every one of them at or under 1.01. At two documents, inlining has already lost on bytes in every case I measured.
The threshold, worked out
Write G for the gzipped size of the document you are adding to, and r for the measured ratio of gzipped-Base64 to file bytes from the table above. Pick a budget — the share of your document you are willing to give away. Then:
largest file worth inlining = budget × G ÷ r
I use a 1% budget. It is a choice, and I am stating it as one rather than presenting it as a law. Here is what it produces, with r taken from the measurements:
| Format | r (gzipped Base64 ÷ file bytes) | Largest file for a 30 KB document | Largest file for a 100 KB document |
|---|---|---|---|
| PNG, photo-like | 0.836 | 367 B | 1,225 B |
| JPEG | 0.872 | 352 B | 1,174 B |
| WebP | 0.965 | 318 B | 1,061 B |
| PNG, flat colour | 0.974 | 315 B | 1,051 B |
| PNG, with alpha | 1.009 | 304 B | 1,015 B |
| PNG, small icon | 1.065 | 288 B | 961 B |
| SVG, text | 1.204 | 255 B | 850 B |
So for a 30 KB document the honest ceiling is 255 to 367 bytes, and it is not one number — the format moves it by 44%. Run the same sum for the quoted 10 KB and see what it means: inlining a 10,240-byte PNG into a 30 KB document adds 10,240 × 0.836 ≈ 8,561 bytes, a 27.9% increase, not a rounding error. The figure people repeat is not a bytes threshold. It is a threshold from a different argument, and this page is deliberately not making that argument — see below.
What this page does not answer
There is a second, separate case for inlining: it removes a request, and requests have their own cost. Whether that cost still exists once connections are multiplexed is a different question with different inputs, and it is not measured here. Everything on this page is the bytes side only, and on the bytes side the answer above is the whole answer. If your reason for inlining is the request, say so — the arithmetic here will not support it either way.
Two related questions also live elsewhere. If you want to know whether your string is damaged, that is Base64 to image. If you want to know which container to save it in — PNG or JPEG, and what each costs — that is Base64 to PNG. And if you just want the encoded string, the converter is on the home page.
How these numbers were produced
Nine samples, generated on this machine and written to disk: a 640 × 360 PNG (5,879 bytes), the same picture as JPEG at quality 92 (15,084) and as WebP at quality 92 (9,254), a 32 × 32 PNG icon (615), a 200 × 200 flat-colour PNG (1,643), a 200 × 200 PNG with alpha (4,951), a 200 × 200 raw RGBA buffer (160,000), a 200 × 200 24-bit BMP (120,054), and a 200 × 200 SVG (167 bytes of text). Each was compressed twice — the file itself, and its Base64 text — with gzip at level 9 and Brotli at quality 11. The ratio column is gzipped-Base64 ÷ gzipped-file; r is gzipped-Base64 ÷ file bytes. The in-browser calculator uses the same gzip level, and I confirmed it returns identical byte counts to the offline runs on the 5,879-byte PNG: 4,652 and 4,914. Brotli is not available through the browser's compression stream, so those figures come from the offline runs only.