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.
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.
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.
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.
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.
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 });
.
.
.