Most image services resize on the way out. You store one big original, ask for /w_400,h_400/photo.jpg, and the service transforms it on the first request and caches the result. It is a lovely model right up until you read the invoice.
The other option is to transform on the way in: decide the shape when the file arrives, store that, and serve plain bytes forever after. Same pictures, very different cost curve.
What each one bills you for
| Transform on download | Transform on upload | |
|---|---|---|
| First request | Slow, a transformation runs | Fast, the file is finished |
| Cache miss | Another transformation, another charge | Nothing to transform |
| New size wanted | A URL change, everything rebuilds | One pass over the originals |
| Charged per | Transformation, storage, bandwidth | Upload, storage, bandwidth |
The line that matters is the second. A transformation cache is still a cache: it expires, it gets evicted, it is cold in a region nobody visited today, and it is empty every time you purge a CDN. Each of those is a fresh transformation, and you are billed for it. The picture has not changed since the day it was uploaded, but you pay to make it again.
Transform on upload and there is exactly one transformation per file, ever. Not one per size per cache miss. One.
The catch, stated honestly
You have to know the sizes up front. A download-time service lets you invent w_312 at three in the morning for a design that ships tomorrow; an upload-time service wants you to have said "avatars are 400x400" beforehand.
In practice that is less painful than it sounds, because you already know. Your design system has a handful of image slots, not a hundred. And when you do need a new size, the originals are still there: define the new template, re-run the files through it, and you have paid one operation per file instead of one per request per size, forever.
What it looks like
A template is the shape, declared once:
curl -X POST https://filemon.io/api/templates \
-H "Authorization: Bearer $FILEMON_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "Avatar", "width": 400, "height": 400, "format": "webp"}'Then every upload through it comes out that shape:
curl -X POST -F "img=@./whatever-they-picked.png" \
-H "Authorization: Bearer $FILEMON_KEY" \
https://filemon.io/api/m1o900k8The URL that comes back is a finished 400x400 webp. No transformation parameters in it, because there is nothing left to transform. The first request is as fast as the millionth, and a cold cache costs a byte transfer rather than a CPU burst.
When download-time is the right answer
If you genuinely do not know the shapes, because your users compose layouts and pick dimensions themselves, transform on download and pay for the flexibility. That is what it is for.
For the far more common case, avatars and product shots and screenshots in a layout you designed, the shapes were decided in Figma months ago. Deciding them again on every cache miss is a strange thing to pay for.