adm-zip, decompress-zip, node-unzip-2, and unzipper are all npm packages designed to handle ZIP archive extraction and manipulation in Node.js environments. These libraries enable developers to read, extract, create, or modify ZIP files programmatically — a common requirement in file processing pipelines, upload handlers, or data import/export features. While they share overlapping goals, their APIs, streaming capabilities, memory usage patterns, and maintenance status differ significantly, which directly impacts suitability for production systems.
Handling ZIP files in Node.js seems straightforward — until you hit real-world constraints like large file sizes, memory limits, or the need for streaming. The four packages under review each take a different approach to ZIP extraction, with significant implications for performance, reliability, and maintainability. Let’s cut through the noise.
Before diving into APIs, address the elephant in the room:
decompress-zip is officially deprecated on npm. Its GitHub repo is archived, and the last commit was in 2016. Do not use it in new code.node-unzip-2 is a community fork of the abandoned node-unzip. Despite occasional npm publishes, its GitHub repository shows minimal activity since 2020 and unresolved critical issues (e.g., ZIP64 incompatibility). Treat it as unmaintained.adm-zip and unzipper are actively maintained, with recent commits addressing security, compatibility, and feature requests.🛑 Rule of thumb: Eliminate
decompress-zipandnode-unzip-2from consideration for any new project. The remaining comparison is effectively betweenadm-zipandunzipper.
How a library handles memory is often the deciding factor.
adm-zip: Full In-Memory Loadingadm-zip loads the entire ZIP file into memory upfront. This simplifies random access to entries but becomes problematic with large archives.
// adm-zip: Entire archive loaded into memory
const AdmZip = require('adm-zip');
const zip = new AdmZip('./archive.zip');
// List all entries
const entries = zip.getEntries();
// Extract a single file to buffer
const data = zip.readFile(entries[0]);
// Write modified archive back to disk
zip.writeZip('./modified.zip');
This works fine for archives under ~100MB, but can exhaust memory on larger files or under high concurrency.
unzipper: True Streamingunzipper provides two streaming approaches:
Extract) for piping entries to disk or other streams.Open) for random access without full loading.// unzipper: Streaming extraction to disk
const fs = require('fs');
const unzipper = require('unzipper');
fs.createReadStream('./archive.zip')
.pipe(unzipper.Extract({ path: './output' }))
.on('close', () => console.log('Done'));
// unzipper: Random access without full load
const directory = await unzipper.Open.file('./archive.zip');
const fileData = await directory.files[0].buffer(); // Only this file loaded
This keeps memory usage proportional to the largest single file in the archive — not the whole ZIP.
Getting file names, sizes, or modification times:
// adm-zip
const zip = new AdmZip('./archive.zip');
zip.getEntries().forEach(entry => {
console.log(entry.entryName, entry.header.time);
});
// unzipper (pull model)
const dir = await unzipper.Open.file('./archive.zip');
dir.files.forEach(file => {
console.log(file.path, file.lastModifiedDateTime);
});
Both work, but adm-zip gives immediate access after construction, while unzipper requires an async open step.
// adm-zip: Extract one file to buffer
const data = zip.readFile('config.json');
// unzipper: Extract one file to buffer
const file = dir.files.find(f => f.path === 'config.json');
const data = await file.buffer();
Again, adm-zip is synchronous once loaded; unzipper is promise-based.
unzipper propagates errors through standard Node.js stream events or promise rejections:
// unzipper: Stream error handling
fs.createReadStream('corrupt.zip')
.pipe(unzipper.Extract({ path: './out' }))
.on('error', err => console.error('Extraction failed:', err));
adm-zip throws exceptions synchronously:
// adm-zip: Try/catch required
try {
const zip = new AdmZip('corrupt.zip');
} catch (err) {
console.error('Invalid ZIP:', err);
}
Only adm-zip supports creating or modifying ZIP files:
// adm-zip: Create new archive
const zip = new AdmZip();
zip.addFile('hello.txt', Buffer.from('Hello World'));
zip.writeZip('./new.zip');
// adm-zip: Modify existing
const zip = new AdmZip('./existing.zip');
zip.deleteFile('old.txt');
zip.addFile('new.txt', Buffer.from('Updated'));
zip.writeZip();
unzipper is extraction-only. If you need to generate ZIPs, adm-zip remains the only option among these four.
unzipperreq.pipe(unzipper.Extract({ path: './uploads' }));
adm-zipconst zip = new AdmZip(configBundle);
const manifest = JSON.parse(zip.readAsText('manifest.json'));
const schema = zip.readFile('schema.bin');
adm-zipconst zip = new AdmZip();
zip.addFile('report.csv', csvBuffer);
zip.addFile('summary.txt', summaryBuffer);
return zip.toBuffer();
| Feature | adm-zip | decompress-zip | node-unzip-2 | unzipper |
|---|---|---|---|---|
| Status | ✅ Maintained | ❌ Deprecated | ⚠️ Unmaintained | ✅ Maintained |
| Memory Model | Full in-memory | Full in-memory | Streaming | Streaming |
| Create/Modify ZIPs | ✅ Yes | ❌ No | ❌ No | ❌ No |
| ZIP64 Support | ⚠️ Partial | ❌ No | ❌ No | ✅ Yes |
| Stream Extraction | ❌ No | ❌ No | ✅ Yes (legacy) | ✅ Yes (modern) |
| Error Handling | Sync exceptions | Unreliable | Stream events | Stream events / Promises |
unzipper. It’s the only actively maintained, streaming-capable, ZIP64-compliant option.adm-zip, but only with small files where memory isn’t a concern.decompress-zip or node-unzip-2 — their risks far outweigh any perceived simplicity.In modern Node.js applications — especially those handling user uploads or processing large datasets — streaming isn’t optional. That makes unzipper the default choice for extraction, while adm-zip fills a narrow but valid niche for archive generation and small-file manipulation.
Avoid node-unzip-2 for new development. Though it offers streaming extraction via Node.js streams, it is a fork of the long-abandoned node-unzip and shows no signs of active maintenance. Critical issues like incomplete ZIP64 support and poor error resilience make it risky for production use.
Choose adm-zip if you need synchronous ZIP operations with full in-memory access to entries (e.g., listing, reading, modifying, or writing archives without streams). It’s well-suited for small-to-medium archives where convenience outweighs memory concerns, but avoid it for large files or streaming scenarios due to its lack of stream support and potential memory pressure.
Avoid decompress-zip in new projects — it is deprecated and unmaintained. The package hasn’t seen updates in years, lacks modern Node.js stream compatibility, and contains unresolved bugs around entry metadata and error handling. Use unzipper instead for similar event-driven extraction patterns.
Choose unzipper when you need robust, streaming-based ZIP extraction with low memory overhead, especially for large archives or server-side file processing. It supports both pull-streams (Open) and push-streams (Extract), handles ZIP64, and provides reliable error propagation. Ideal for scalable applications where memory efficiency and maintainability matter.
Streaming cross-platform unzip tool written in node.js. It is an improved version of Evan Oxfeld's node-unzip, which supports unzipping for files with a "STORE" compression (uncompressed files).
Unzip provides simple APIs similar to node-tar for parsing and extracting zip files. There are no added compiled dependencies - inflation is handled by node.js's built in zlib support. Unzip is also an example use case of node-pullstream.
$ npm install node-unzip-2
fs.createReadStream('path/to/archive.zip').pipe(unzip.Extract({ path: 'output/path' }));
Extract emits the 'close' event once the zip's contents have been fully extracted to disk.
Process each zip file entry or pipe entries to another stream.
Important: If you do not intend to consume an entry stream's raw data, call autodrain() to dispose of the entry's contents. Otherwise you risk running out of memory.
fs.createReadStream('path/to/archive.zip')
.pipe(unzip.Parse())
.on('entry', function (entry) {
var fileName = entry.path;
var type = entry.type; // 'Directory' or 'File'
var size = entry.size;
if (fileName === "this IS the file I'm looking for") {
entry.pipe(fs.createWriteStream('output/path'));
} else {
entry.autodrain();
}
});
Or pipe the output of unzip.Parse() to fstream
var readStream = fs.createReadStream('path/to/archive.zip');
var writeStream = fstream.Writer('output/path');
readStream
.pipe(unzip.Parse())
.pipe(writeStream)
(The MIT License)
Copyright (c) 2012 - 2013 Near Infinity Corporation
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.