adm-zip, extract-zip, node-unzip-2, and unzipper are all npm packages designed to work with ZIP archives in Node.js environments. They enable developers to extract, read, or manipulate ZIP files programmatically. While they share the common goal of ZIP file handling, they differ significantly in architecture, streaming support, memory usage, API design, and maintenance status. These differences directly impact their suitability for tasks ranging from simple one-off extractions to high-throughput server-side processing.
Working with ZIP files in Node.js seems straightforward until you hit real-world constraints: large archives, memory limits, streaming requirements, or the need to modify archives on the fly. The four packages under review — adm-zip, extract-zip, node-unzip-2, and unzipper — each take a different approach to solving these problems. Let’s cut through the noise and compare them based on how they actually behave in practice.
First, a critical warning: node-unzip-2 is deprecated. Its GitHub repository explicitly states it’s no longer maintained, and the npm page shows it hasn’t been updated in years. It relies on outdated stream implementations and has unresolved security issues. Do not use it in new projects. We include it here only because it still appears in legacy codebases, but unzipper was created as its spiritual successor and should be used instead.
The biggest technical divide lies in how these libraries handle data flow.
adm-zip loads the entire ZIP file into memory before doing anything. This makes its API simple but dangerous for large files.
// adm-zip: Entire file loaded into RAM
const AdmZip = require('adm-zip');
const zip = new AdmZip('./archive.zip');
zip.extractAllTo('./output'); // Synchronous, blocks event loop
extract-zip uses yauzl under the hood, which parses ZIP files without loading everything into memory, but extract-zip itself doesn’t expose streaming — it just writes extracted files to disk.
// extract-zip: Extracts to disk, no streaming control
const extract = require('extract-zip');
await extract('./archive.zip', { dir: './output' });
unzipper is built for streaming from the ground up. It supports both event-based parsing and true Node.js streams, enabling memory-efficient processing.
// unzipper: Stream individual entries
const fs = require('fs');
const unzipper = require('unzipper');
fs.createReadStream('./archive.zip')
.pipe(unzipper.Parse())
.on('entry', (entry) => {
const filePath = `./output/${entry.path}`;
entry.pipe(fs.createWriteStream(filePath));
});
Your choice depends heavily on whether you can afford blocking operations.
adm-zip is mostly synchronous, which is convenient for scripts but disastrous in servers:
// adm-zip: Blocking I/O
const zip = new AdmZip('./large-file.zip');
zip.extractEntryTo('file.txt', './output'); // Freezes entire process
extract-zip provides a Promise-based async API, but only for full extraction:
// extract-zip: Async but all-or-nothing
await extract('./archive.zip', { dir: './output' });
unzipper gives you multiple async options: Promises for simple cases, streams for complex ones:
// unzipper: Promise-based extraction (like extract-zip)
await unzipper.Open.file('./archive.zip').then(d => d.extract({ path: './output' }));
// Or full streaming control
fs.createReadStream('./archive.zip')
.pipe(unzipper.Extract({ path: './output' }));
If you’re dealing with ZIP files larger than a few hundred MB, memory becomes critical.
adm-zip: Will crash your process if the ZIP file exceeds available RAM. No workaround.extract-zip: Handles large files reasonably well because yauzl reads incrementally, but you can’t process entries individually.unzipper: Designed for gigabyte-sized archives. Streams decompress data on the fly without buffering entire entries.Example of memory-safe large file handling with unzipper:
// Process huge ZIP without blowing memory
fs.createReadStream('huge-archive.zip')
.pipe(unzipper.Parse())
.on('entry', (entry) => {
if (entry.path === 'target-file.txt') {
entry.pipe(process.stdout); // Stream directly to output
} else {
entry.autodrain(); // Discard unwanted entries safely
}
});
Neither adm-zip nor extract-zip can do this efficiently.
| Capability | adm-zip | extract-zip | node-unzip-2 | unzipper |
|---|---|---|---|---|
| Extract to disk | ✅ | ✅ | ✅ | ✅ |
| Extract to memory | ✅ | ❌ | ✅ | ✅ |
| Create/modify ZIPs | ✅ | ❌ | ❌ | ✅ (limited) |
| Streaming extraction | ❌ | ❌ | ⚠️ (legacy) | ✅ |
| Password-protected ZIPs | ❌ | ❌ | ❌ | ✅ |
| Async / non-blocking | ❌ | ✅ | ⚠️ | ✅ |
| Large file support | ❌ | ✅ | ⚠️ | ✅ |
Creating ZIP files with unzipper:
// unzipper: Create ZIPs (basic support)
const { ZipFile } = require('unzipper');
const zip = new ZipFile();
zip.add('file.txt', 'Hello world');
const buffer = await zip.buffer();
adm-zip has more mature creation APIs:
// adm-zip: Full ZIP creation
const zip = new AdmZip();
zip.addFile('test.txt', Buffer.from('hello'));
zip.writeZip('new.zip'); // Synchronous write
Error handling varies significantly:
adm-zip: Throws exceptions on invalid ZIPs; hard to recover from.extract-zip: Rejects promises with clear errors; reliable for basic extraction.unzipper: Emits 'error' events on streams and rejects promises appropriately. Handles malformed ZIPs gracefully.Example with unzipper error handling:
const stream = fs.createReadStream('corrupt.zip')
.pipe(unzipper.Parse())
.on('error', (err) => {
console.error('ZIP parsing failed:', err);
});
adm-zip would crash the process on the same file.
You’re writing a CLI tool that downloads and extracts a ZIP of assets.
extract-zip// Clean and minimal
await extract(downloadPath, { dir: targetDir });
Users upload ZIPs that your server must validate and extract selectively.
unzipper// Handle uploads safely
req.pipe(unzipper.Parse())
.on('entry', (entry) => {
if (isSafePath(entry.path)) {
entry.pipe(writeToStorage(entry.path));
} else {
entry.autodrain();
}
});
You need to add, remove, or update files inside existing ZIP archives.
adm-zip// Modify existing archive
const zip = new AdmZip('mod.zip');
zip.deleteFile('old-config.txt');
zip.addFile('new-config.txt', configBuffer);
zip.writeZip();
You inherited code using node-unzip-2.
unzipper immediatelyMigration example:
// Before (node-unzip-2)
fs.createReadStream('file.zip').pipe(unzip.Extract({ path: 'out' }));
// After (unzipper)
fs.createReadStream('file.zip').pipe(unzipper.Extract({ path: 'out' }));
| Package | Best For | Avoid When | Memory Safety | Async Ready |
|---|---|---|---|---|
adm-zip | ZIP creation/modification, scripts | Large files, servers | ❌ | ❌ |
extract-zip | Simple disk extraction | Need streaming or in-memory | ✅ | ✅ |
node-unzip-2 | Never use in new projects | Always | ⚠️ | ⚠️ |
unzipper | Servers, large files, streaming | Need advanced ZIP creation | ✅ | ✅ |
unzipper is the right default choice. It’s safe, efficient, and flexible.adm-zip if you absolutely need to create or edit ZIP archives and can guarantee small file sizes.extract-zip when you want the simplest possible “extract this ZIP to that folder” solution with zero configuration.node-unzip-2 — its time has passed.The key is matching the tool to your actual constraints: memory limits, file size, need for streaming, and whether you’re reading or writing archives. Get that right, and ZIP handling stops being a headache.
Choose adm-zip if you need synchronous ZIP operations, full archive manipulation (create, update, delete entries), and don’t require streaming or large-file support. It loads entire archives into memory, making it unsuitable for large files or memory-constrained environments. Its synchronous nature also blocks the event loop, so avoid it in performance-sensitive server code.
Choose extract-zip if your only requirement is extracting ZIP files to disk with minimal setup and you’re working in a standard Node.js environment. It’s a thin wrapper around yauzl that handles common extraction tasks reliably but offers no streaming, in-memory access, or archive modification capabilities. Ideal for CLI tools or build scripts where simplicity matters most.
Do not choose node-unzip-2 for new projects. The package is deprecated and unmaintained, with known security vulnerabilities and compatibility issues with modern Node.js versions. While it once provided streaming extraction via legacy streams, its risks and lack of updates make it unsafe for production use. Evaluate unzipper as a direct replacement instead.
Choose unzipper if you need streaming extraction, low memory usage, support for large files, or integration with Node.js streams and async iterators. It supports both pull-based (event-driven) and push-based (streaming) APIs, handles password-protected archives, and works well in server environments. Use it when performance, scalability, or memory efficiency are priorities.
ADM-ZIP is a pure JavaScript implementation for zip data compression for NodeJS.
With npm do:
$ npm install adm-zip
Electron file system support described below.
The library allows you to:
There are no other nodeJS libraries that ADM-ZIP is dependent of
var AdmZip = require("adm-zip");
// reading archives
var zip = new AdmZip("./my_file.zip");
var password = "1234567890";
var zipEntries = zip.getEntries(); // an array of ZipEntry records - add password parameter if entries are password protected
zipEntries.forEach(function (zipEntry) {
console.log(zipEntry.toString()); // outputs zip entries information
if (zipEntry.entryName == "my_file.txt") {
console.log(zipEntry.getData().toString("utf8"));
}
});
// outputs the content of some_folder/my_file.txt
console.log(zip.readAsText("some_folder/my_file.txt"));
// extracts the specified file to the specified location
zip.extractEntryTo(/*entry name*/ "some_folder/my_file.txt", /*target path*/ "/home/me/tempfolder", /*maintainEntryPath*/ false, /*overwrite*/ true);
// extracts everything
zip.extractAllTo(/*target path*/ "/home/me/zipcontent/", /*overwrite*/ true);
// creating archives
var zip = new AdmZip();
// add file directly
var content = "inner content of the file";
zip.addFile("test.txt", Buffer.from(content, "utf8"), "entry comment goes here");
// add local file
zip.addLocalFile("/home/me/some_picture.png");
// get everything as a buffer
var willSendthis = zip.toBuffer();
// or write everything to disk
zip.writeZip(/*target file name*/ "/home/me/files.zip");
// ... more examples in the wiki
For more detailed information please check out the wiki.
ADM-ZIP has supported electron original-fs for years without any user interractions but it causes problem with bundlers like rollup etc. For continuing support original-fs or any other custom file system module. There is possible specify your module by fs option in ADM-ZIP constructor.
Example:
const AdmZip = require("adm-zip");
const OriginalFs = require("original-fs");
// reading archives
const zip = new AdmZip("./my_file.zip", { fs: OriginalFs });
.
.
.