Most "free HEIC to JPG" tools work the same way: you upload your iPhone photos to someone's server, it converts them, you download the result. For a format conversion — one of the most local operations imaginable — that's a strange trade.
I wanted HEIC to JPG support in SnapShrink (my in-browser batch image compressor) without breaking its core promise: nothing ever uploads. Here's how I did it.
The decoder: heic2any, lazy-loaded
Browsers can't decode HEIC natively (unlike AVIF, which Chrome/Edge/Firefox handle via createImageBitmap). So the conversion needs a real decoder. I used heic2any, pinned to a fixed version, loaded via dynamic import() only when a HEIC file is actually dropped.
That last part matters: the decoder is a chunk of WASM/JS you don't want in the initial bundle for the 95% of users who never touch HEIC. Lazy-loading keeps the first paint fast for everyone.
The pipeline
Once decoded, a HEIC file is just pixels — it flows into the exact same pipeline as every other format:
- Decode → ImageData/canvas
- Quality slider or Target KB mode (binary-search the quality setting until the output fits)
- Encode to JPG/PNG/WebP, batch, ZIP download
No HEIC-specific special cases downstream. That was the whole point of the architecture: new input formats are a decode step, not a new app. AVIF support later followed the same path — same idea, native decode.
The UX details that matter
- Files get a small "HEIC" badge in the drop list so users can see what was detected.
- A misnamed file (
.heicthat's actually a JPEG) gets a clear error instead of a cryptic decoder failure. - Target KB mode works on HEIC too — type
100, get ≤100KB. This is the feature people actually write in about: forms with hard size caps. (How it works.)
Try it
Drop a real iPhone photo on getsnapshrink.com/heic-to-jpg/ and watch DevTools → Network while it converts. Zero file uploads — that's the entire architecture, and HEIC didn't change it.
First 10 images per session free, unlimited is a one-time $9. Happy to answer questions about the implementation in the comments.
Top comments (1)
nice. one builder's question on the misnamed-file error: does it sniff magic bytes or just the extension? the files people misname are exactly the ones an extension check misses.