A comparison of JavaScript libraries for client-side HEIC conversion. Six candidates, split by environment, one clear pick.
Apple made HEIC the default photo format in iOS 11 (2017). Every iPhone since then shoots HEIC by default. Our user base is 63% iPhone. When those users try to upload a profile picture during onboarding, they are sending us a file format most browsers cannot decode.
There is no browser API to convert HEIC server-side before upload, and adding backend HEIC support means touching storage pipelines, image processing queues, and CDN handling. The simpler path: convert on the client before upload. The browser already has Canvas. We just need a decoder.
Our goal is client-side HEIC → PNG conversion in the browser. That means the library must run in a browser environment. This immediately splits the candidates into two groups:
We include sharp and wasm-vips for completeness since they are often mentioned in HEIC discussions. They are not viable for our client-side use case.
All browser-based options wrap libheif (the reference C++ decoder) compiled to WebAssembly. sharp wraps libvips with native bindings. magick-wasm wraps ImageMagick to WASM. The differences matter.
| heic-to | heic2any | libheif-js | magick-wasm | sharp | |
|---|---|---|---|---|---|
| Repository | hoppergee/heic-to | alexcorvi/heic2any | catdad-experiments/libheif-js | dlemstra/magick-wasm | lovell/sharp |
| Environment | Browser | Browser | Browser | Browser + Node | Node.js only |
| License | LGPL-3.0 | MIT (disputed) | LGPL-3.0 | Apache-2.0 | Apache-2.0 |
| GitHub Stars | 320 | 858 | 113 | 853 | 32K |
| Open Issues | 8 | 24 | 6 | 7 | 121 |
| npm Weekly Downloads | 307.7K | 823.4K | 725.3K | 45.8K | 60.8M |
| Created | Aug 2024 | Jun 2019 | Jan 2020 | Aug 2019 | Aug 2013 |
| Last Release | v1.5.2 — May 2026 | v0.0.4 — Mar 2023 | v1.19.8 — Jun 2025 | v0.0.40 — Apr 2026 | v0.34.2 — Mar 2026 |
| Releases (total) | 31 | 4 | 16 | 40 | 107 |
| Contributors | 9 | 3 | 3 | 20 | 220 |
| Underlying Decoder | libheif 1.22.2 | libheif (outdated) | libheif 1.19.8 | ImageMagick 7.1.2 + libheif 1.21.2 | libvips + libheif |
| Output Formats | PNG, JPEG, Bitmap | PNG, JPEG, GIF | Raw pixels (Canvas) | 100+ formats | JPEG, PNG, WebP, AVIF, GIF, TIFF |
| TypeScript | Yes | Partial | Yes | Yes | Yes |
| Web Worker Support | Yes (heic-to/next) | Partial | No | Manual | N/A (server) |
| CSP Compatible | Yes (heic-to/csp) | No | Yes | Yes | N/A (server) |
| HEIC Detection | Yes (isHeic) | No | No | No | Auto-detect |
| Dependencies | 0 | 0 | 0 | 0 | 0 (native) |
What your users actually download. The browser-based libraries ship a WASM-compiled decoder. sharp ships native binaries per platform. magick-wasm ships a 13.8 MB WASM binary.
| Minified | Gzipped | WASM Binary | Unpacked (npm) | |
|---|---|---|---|---|
| heic-to | 2.96 MB | 718 KB | ~1 MB (libheif) | 23.2 MB |
| heic2any | 1.32 MB | 333 KB | ~1 MB (bundled) | 2.6 MB |
| libheif-js | 2.08 MB | 507 KB | 1.0 MB (.wasm) | 6.1 MB |
| magick-wasm | 212 KB (JS only) | 60 KB (JS only) | 13.8 MB | 14.5 MB |
| sharp | N/A (native) | N/A | N/A (native binaries) | ~50 MB (platform-specific) |
The browser libraries split into three license camps:
| Library | License | Verdict |
|---|---|---|
| heic-to, libheif-js | LGPL-3.0 (via libheif) | Standard for WASM-wrapped C libraries. LGPL requires users can replace the library. In a browser, the WASM binary is downloaded at runtime — users already have it. Load from a CDN chunk for extra compliance comfort. |
| heic2any | MIT (on paper) | Disputed. Bundles libheif (LGPL) inside its distribution. The libheif maintainer flagged this as an LGPL violation in issue #59. An MIT license over LGPL code does not make the LGPL go away. |
| magick-wasm | Apache-2.0 | Clean. Apache-2.0 is permissive. No issues. |
| sharp | Apache-2.0 | Clean. Dominant in the Node.js ecosystem. No issues. |
How this looks in practice with heic-to. Lazy-loaded, only when needed.
// Only loaded when a HEIC file is detected — zero cost otherwise
async function convertHeicToPng(file: File): Promise<Blob> {
const { isHeic, heicTo } = await import("heic-to");
if (!await isHeic(file)) {
return file; // already a supported format
}
return heicTo({
blob: file,
type: "image/png",
quality: 1,
});
}
// Usage in your upload handler
const processed = await convertHeicToPng(selectedFile);
// Upload `processed` as usual
For CSP-restricted environments, swap the import:
const { isHeic, heicTo } = await import("heic-to/csp");
For offloading to a Web Worker (keeps the main thread responsive on mobile):
const { heicTo } = await import("heic-to/next");
heic-to is the only library that is actively maintained, tracks upstream libheif releases, and provides the integration features we need (CSP, Workers, HEIC detection) out of the box.
The LGPL license is standard for WASM-wrapped C libraries in web contexts. If it is a concern, load from a CDN chunk to satisfy the "user can replace" requirement.
Why not the others?
Install: npm install heic-to