Working with HEIC Files in JavaScript and Node.js
Learn how to decode, convert, and process HEIC images in JavaScript and Node.js using libheif-js, sharp, and browser-based Web Workers.
JavaScript can decode, convert, and process HEIC files on both client and server. The ecosystem includes WebAssembly-based browser libraries, native Node.js bindings, and purpose-built conversion packages. This guide covers the practical tools, code patterns, and architecture decisions for integrating HEIC support into JavaScript applications.
For background on the format, see What Is HEIC?. For browser compatibility details, see HEIC Browser Support.
Detecting HEIC Files by Magic Bytes
The most reliable HEIC detection reads the file's binary signature. MIME types and file extensions can be missing or incorrect. Magic bytes are definitive.
HEIC files use the ISO Base Media File Format (ISOBMFF) container. Bytes 4-11 contain the ftyp box followed by a brand identifier. The brand determines the exact format variant.
async function isHeicFile(file) {
const buffer = await file.slice(0, 12).arrayBuffer();
const view = new Uint8Array(buffer);
// Bytes 4-7 must be "ftyp"
const ftyp = String.fromCharCode(view[4], view[5], view[6], view[7]);
if (ftyp !== 'ftyp') return false;
// Bytes 8-11 contain the brand
const brand = String.fromCharCode(view[8], view[9], view[10], view[11]);
const heicBrands = ['heic', 'heix', 'hevc', 'hevx', 'heim', 'heis', 'mif1'];
return heicBrands.includes(brand);
}
The heic brand indicates single-image HEVC. The heix brand indicates multi-image HEVC. The mif1 brand is the generic HEIF container used by some Android devices. Checking all 7 brands covers 99%+ of HEIC files in the wild.
Combine magic byte detection with MIME type and extension checks for maximum coverage:
function getHeicMimeTypes() {
return ['image/heic', 'image/heif', 'image/heic-sequence', 'image/heif-sequence'];
}
function hasHeicExtension(filename) {
const ext = filename.split('.').pop()?.toLowerCase();
return ['heic', 'heif', 'heics'].includes(ext);
}
JavaScript Library Comparison
Five libraries handle HEIC processing in JavaScript. Each targets a different environment and use case.
| Library | Environment | HEIC Decode | Convert To | Bundle Size | Key Feature | | --- | --- | --- | --- | --- | --- | | libheif-js | Browser, Node.js | Yes | Canvas/raw pixels | ~1.2 MB (wasm) | Full decoder, multi-image support | | heic2any | Browser | Yes | JPG, PNG, GIF | ~1.3 MB (includes libheif-js) | Simple blob-to-blob API | | sharp | Node.js | Yes | JPG, PNG, WebP, AVIF | Native binary | Fastest server-side processing | | heic-convert | Node.js | Yes | JPG, PNG | ~800 KB | Lightweight, no native deps | | libheif (C++) | Native/WASM | Yes | Raw pixels | N/A | Reference implementation |
sharp is the best choice for Node.js servers. It processes a 12 MP HEIC file in 200-300ms. libheif-js is the best choice for browser applications. It provides full decoder access via WebAssembly. heic2any wraps libheif-js with a simpler API at the cost of less control.
Node.js: Processing HEIC with Sharp
Sharp is the fastest way to handle HEIC in Node.js. It uses libvips with libheif bindings for native-speed decoding. Install it with pnpm add sharp (version 0.33+ for full HEIC support).
Reading and Converting a HEIC File
import sharp from 'sharp';
import { readFile } from 'fs/promises';
async function convertHeicToJpg(inputPath, outputPath) {
const buffer = await readFile(inputPath);
await sharp(buffer)
.jpeg({ quality: 85, mozjpeg: true })
.toFile(outputPath);
}
async function convertHeicToWebP(inputPath, outputPath) {
const buffer = await readFile(inputPath);
await sharp(buffer)
.webp({ quality: 80 })
.toFile(outputPath);
}
Sharp detects HEIC input automatically from the file header. No format-specific configuration is required.
Extracting Metadata
async function getHeicMetadata(inputPath) {
const metadata = await sharp(inputPath).metadata();
return {
width: metadata.width, // e.g., 4032
height: metadata.height, // e.g., 3024
format: metadata.format, // "heif"
space: metadata.space, // "srgb" or "p3"
hasAlpha: metadata.hasAlpha, // false for photos
exif: metadata.exif, // raw EXIF buffer
};
}
Sharp exposes EXIF, ICC profile, and XMP data from HEIC files. For detailed metadata handling, see HEIC Metadata and EXIF Data.
Batch Processing
import { glob } from 'fs/promises';
async function batchConvert(inputDir, outputDir, format = 'webp') {
const files = await glob(`${inputDir}/*.{heic,HEIC}`);
const results = await Promise.allSettled(
files.map(async (file) => {
const name = file.split('/').pop().replace(/\.heic$/i, `.${format}`);
await sharp(file)
.resize(2048, 2048, { fit: 'inside', withoutEnlargement: true })
[format]({ quality: 80 })
.toFile(`${outputDir}/${name}`);
return name;
})
);
return results;
}
Sharp processes files in parallel using libuv's thread pool. Set UV_THREADPOOL_SIZE=16 for batch workloads. The default pool size of 4 threads underutilizes modern CPUs.
Node.js Alternative: heic-convert
heic-convert is a pure JavaScript alternative for environments where native bindings are impractical. It requires no compiled dependencies, making it suitable for serverless functions with strict binary constraints.
import convert from 'heic-convert';
import { readFile, writeFile } from 'fs/promises';
async function convertWithHeicConvert(inputPath, outputPath) {
const inputBuffer = await readFile(inputPath);
const outputBuffer = await convert({
buffer: inputBuffer,
format: 'JPEG',
quality: 0.85,
});
await writeFile(outputPath, Buffer.from(outputBuffer));
}
heic-convert is 3-5x slower than sharp for the same file. Use it only when native dependencies are not an option.
Browser: Client-Side HEIC Conversion
Client-side HEIC conversion eliminates server uploads and protects user privacy. The browser never sends photo data over the network. Two libraries make this possible.
libheif-js: Full Decoder Access
libheif-js compiles the reference C++ libheif library to WebAssembly. It provides low-level control over the decoding pipeline.
import libheif from 'libheif-js';
async function decodeHeicToCanvas(arrayBuffer) {
const decoder = new libheif.HeifDecoder();
const images = decoder.decode(new Uint8Array(arrayBuffer));
const image = images[0];
const width = image.get_width();
const height = image.get_height();
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
const imageData = ctx.createImageData(width, height);
await new Promise((resolve) => {
image.display(imageData, (result) => resolve(result));
});
ctx.putImageData(imageData, 0, 0);
return canvas;
}
The decoder.decode() call returns an array because HEIC containers can hold multiple images. Single-photo files return an array with 1 element. Burst shots and image sequences return multiple elements.
heic2any: Simplified API
heic2any wraps libheif-js and provides a single-function conversion interface.
import heic2any from 'heic2any';
async function heicToJpgBlob(file) {
const jpgBlob = await heic2any({
blob: file,
toType: 'image/jpeg',
quality: 0.85,
});
return jpgBlob;
}
heic2any returns a single Blob for single-image files and an array of Blobs for multi-image containers. Always handle both return types in production code.
Web Workers: Non-Blocking Conversion
HEIC decoding blocks the main thread for 1-3 seconds per 12 MP image. Web Workers move this CPU-intensive work to a background thread, keeping the UI responsive.
Worker Architecture
// heic-worker.js
import heic2any from 'heic2any';
self.onmessage = async (event) => {
const { fileData, quality, outputType } = event.data;
try {
const blob = new Blob([fileData]);
const result = await heic2any({
blob,
toType: outputType || 'image/jpeg',
quality: quality || 0.85,
});
const buffer = await result.arrayBuffer();
self.postMessage({ success: true, buffer }, [buffer]);
} catch (error) {
self.postMessage({ success: false, error: error.message });
}
};
Main Thread Integration
function convertInWorker(file, quality = 0.85) {
return new Promise((resolve, reject) => {
const worker = new Worker('./heic-worker.js', { type: 'module' });
worker.onmessage = (event) => {
worker.terminate();
if (event.data.success) {
resolve(new Blob([event.data.buffer], { type: 'image/jpeg' }));
} else {
reject(new Error(event.data.error));
}
};
file.arrayBuffer().then((buffer) => {
worker.postMessage(
{ fileData: buffer, quality },
[buffer]
);
});
});
}
Transfer the ArrayBuffer using the transferable objects API (the second argument to postMessage). This moves the buffer to the worker without copying, reducing memory usage by 50% per file.
For batch conversion, spawn multiple workers up to navigator.hardwareConcurrency (typically 4-16 on modern devices). Each worker processes one file independently.
Performance Benchmarks
WebAssembly HEIC decoding is 5-10x slower than native C++ decoding. This tradeoff is acceptable for client-side tools where privacy and zero-upload architecture matter more than raw speed.
Decode Times for a 12 MP HEIC File (4032 x 3024)
| Environment | Library | Decode Time | Total Conversion | | --- | --- | --- | --- | | Node.js (native) | sharp | ~200 ms | ~300 ms to JPG | | Node.js (JS) | heic-convert | ~800 ms | ~1,100 ms to JPG | | Browser (desktop) | libheif-js | ~1,500 ms | ~2,000 ms to canvas | | Browser (desktop) | heic2any | ~1,800 ms | ~2,500 ms to JPG blob | | Browser (mobile) | libheif-js | ~3,500 ms | ~4,500 ms to canvas |
Memory Usage
A 12 MP image at 4 bytes per pixel (RGBA) consumes 48 MB of uncompressed bitmap data. A 48 MP image consumes 192 MB. Monitor performance.memory (Chrome) or set explicit file size limits to prevent out-of-memory crashes on mobile devices.
Client-Side vs. Server-Side: Architecture Tradeoffs
The right architecture depends on privacy requirements and processing volume.
| Factor | Client-Side | Server-Side | | --- | --- | --- | | Privacy | Files never leave the device | Files uploaded to server | | Speed | 1.5-4.5 seconds per image | 200-300 ms per image | | Scalability | Uses visitor's CPU | Requires server resources | | Batch performance | Limited by device cores | Scales with server fleet | | Mobile performance | 2-3x slower than desktop | Consistent across clients | | Dependencies | ~1.2 MB WASM download | Native binary on server | | Offline support | Works after initial load | Requires network connection |
Choose client-side when privacy is the primary concern. Users uploading personal photos, medical images, or confidential documents benefit from zero-upload processing. The performance cost is acceptable for individual files.
Choose server-side when processing volume or speed matters. Image pipelines handling thousands of uploads per hour need sharp's native performance. CDN integration and multi-format output generation are simpler on the server.
HEICify's Architecture
HEICify uses client-side WebAssembly with Web Workers. This architecture processes HEIC files entirely in the browser. No files are uploaded to any server. No image data leaves the user's device.
The conversion pipeline works in 3 stages:
- File detection -- react-dropzone accepts the file and validates it as HEIC by extension and magic bytes.
- Worker dispatch -- the file's ArrayBuffer is transferred to a Web Worker running libheif-js.
- Canvas rendering -- the decoded pixel data is rendered to a canvas element, then exported as JPG or PNG at the user's chosen quality level.
Multiple Web Workers process batch conversions in parallel. Progress tracking runs on the main thread via postMessage callbacks from each worker. This keeps the UI responsive even during 50+ file batch operations.
For the full developer perspective on HEIC web compatibility, see HEIC for Web Developers. To convert HEIC files without writing code, use HEICify's HEIC to JPG converter or HEIC to PNG converter -- both process files entirely in your browser with zero server uploads.
Frequently Asked Questions
What JavaScript library converts HEIC files?
Can I convert HEIC in the browser without a server?
How do I process HEIC files in Node.js?
Is HEIC conversion slow in JavaScript?
Related Guides
HEIC Browser Support in 2026: What Works Where
Complete guide to HEIC browser support in 2026 covering Chrome, Safari, Firefox, Edge, and mobile browsers with compatibility tables and fallback strategies.
How HEIC Compression Works: A Technical Guide
Understand HEIC compression technology including HEVC intra-frame coding, transform blocks, quantization, and why it achieves 50% smaller files than JPEG.
HEIC on the Web: Browser Support and Best Practices
A developer's guide to handling HEIC files in web applications — browser support status, server-side conversion, client-side processing, and content negotiation strategies.
HEIC Metadata and EXIF Data: What Transfers When You Convert
Understand what metadata and EXIF data HEIC files contain, what survives conversion to JPG or PNG, and how to preserve or remove metadata during conversion.
What is HEIC Format? Everything You Need to Know
Learn what HEIC is, why Apple uses it, how it compares to JPG, and the easiest ways to open or convert HEIC files on any device.
Ready to Convert Your Images?
Try our free, browser-based converter tools. No uploads required -- your files never leave your device.