HEIC → PNG Conversion

A comparison of JavaScript libraries for client-side HEIC conversion. Six candidates, split by environment, one clear pick.

Why this matters

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.

63%
of app users are on iPhone
2017
Apple adopted HEIC as default
0
browsers with native HEIC support

Scope

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:

Browser-native
heic-to, heic2any, libheif-js, magick-wasm
These compile libheif or ImageMagick to WASM and run in the browser.
Node.js only
sharp, wasm-vips (partial)
These require a server. sharp is native bindings; wasm-vips has experimental browser support.

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.

The candidates

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)

Bundle size

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)
Note: heic-to's unpacked size (23 MB) is large because it ships multiple build variants (CSP, IIFE, Worker) and source files. The actual runtime bundle your bundler produces is the minified column. With dynamic import, this is only loaded when a HEIC file is detected.
On magick-wasm: The JS wrapper is tiny (60 KB gzipped), but the WASM binary is 13.8 MB. That is 19x larger than heic-to's entire gzipped bundle. It is a general-purpose image toolkit — bringing a cannon to a knife fight.
On heic2any: Appears smaller, but bundles an older, smaller libheif. heic-to ships the latest libheif (1.22.2) which supports newer HEIC profiles from iPhone 15/16. The extra kilobytes buy actual format support.

License reality check

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.
Bottom line: LGPL in a dynamically-imported frontend WASM library is widely accepted. heic2any's MIT claim over bundled LGPL code is the actual licensing risk in this comparison. magick-wasm and sharp have the cleanest licenses.

Library analysis

heic-to Recommended

Strengths

  • Actively maintained — 31 releases, last one 3 weeks ago
  • Tracks upstream libheif releases (currently 1.22.2)
  • Built-in isHeic() detection helper
  • CSP-compatible build (heic-to/csp)
  • First-class Web Worker support (heic-to/next)
  • TypeScript declarations included
  • 9 contributors, bus factor > 1
  • Zero dependencies

Tradeoffs

  • LGPL-3.0 license (see above)
  • Smaller community than heic2any (320 vs 858 stars)
  • Gzipped bundle is ~718 KB
  • Newer project (created Aug 2024)

heic2any Not Recommended

Strengths

  • Most popular by downloads (823K/week)
  • Simple API — one function, done
  • Can convert bursts to animated GIF

Tradeoffs

  • Abandoned — last release March 2023 (3+ years)
  • Only 4 releases total in 7 years
  • Bundles outdated libheif, fails on iPhone 15/16 HEIC
  • 24 open issues, no maintainer response
  • License violation flagged by libheif maintainer
  • No CSP support, no Web Worker support
  • No HEIC detection helper

libheif-js Viable but Raw

Strengths

  • Most established decoder — used by heic2any and others
  • Smallest gzipped bundle (507 KB)
  • Full decoder access — maximum control
  • Works in Node.js too

Tradeoffs

  • Decoder only — no PNG/JPEG export, you write the Canvas code
  • Last release Jun 2025, slower update cadence
  • No isHeic helper, no CSP build, no Worker support
  • Lower-level API = more code to maintain

magick-wasm Overkill

Strengths

  • Full ImageMagick in the browser — 100+ formats
  • Actively maintained, Apache-2.0 license
  • Clean license, no LGPL concerns
  • TypeScript, 20 contributors
  • If you also need resize, rotate, filters, etc.

Tradeoffs

  • WASM binary is 13.8 MB — 19x heic-to's gzipped bundle
  • Brings ImageMagick's full weight for a single HEIC → PNG task
  • JS wrapper is tiny (60 KB gzip) but WASM is the bottleneck
  • Smaller community for this specific use case (45K downloads/week)
  • HEIC support had stability issues historically (issue #22)

sharp Node.js Only

Strengths

  • 32K stars, 60M weekly downloads — the standard
  • Fastest image processing in the Node.js ecosystem
  • HEIC via libvips, auto-detected from file header
  • Apache-2.0, clean license
  • 220 contributors, battle-tested at scale

Tradeoffs

  • Node.js only — does not run in the browser
  • Requires native binaries (not WASM)
  • WebAssembly variant exists (@webassembly/sharp) but is unsupported and experimental
  • Would require backend changes to support HEIC upload flow

Integration example

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");

Recommendation

Use heic-to

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?

  • heic2any was the answer two years ago. 24 open issues, no releases since March 2023, fails on iPhone 15/16, and a license problem with its upstream dependency.
  • libheif-js is a solid decoder, but you would be writing Canvas export boilerplate that heic-to already ships.
  • magick-wasm has a clean license and 100+ formats, but ships a 13.8 MB WASM binary. That is a 19x size penalty for functionality we do not need.
  • sharp is the gold standard for Node.js image processing, but it does not run in the browser. Using it would mean backend changes we are specifically trying to avoid.

Install: npm install heic-to

Sources