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 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.
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 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.
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.
Unzip written in pure JavaScript. Extracts a zip into a directory. Available as a library or a command line program.
Uses the yauzl ZIP parser.
Make sure you have Node 10 or greater installed.
Get the library:
npm install extract-zip --save
Install the command line program:
npm install extract-zip -g
const extract = require('extract-zip')
async function main () {
try {
await extract(source, { dir: target })
console.log('Extraction complete')
} catch (err) {
// handle any errors
}
}
dir (required) - the path to the directory where the extracted files are writtendefaultDirMode - integer - Directory Mode (permissions), defaults to 0o755defaultFileMode - integer - File Mode (permissions), defaults to 0o644onEntry - function - if present, will be called with (entry, zipfile), entry is every entry from the zip file forwarded from the entry event from yauzl. zipfile is the yauzl instanceDefault modes are only used if no permissions are set in the zip file.
extract-zip foo.zip <targetDirectory>
If not specified, targetDirectory will default to process.cwd().