# archiver: building ZIP and TAR archives as Node.js streams

> archiver is an MIT-licensed JavaScript library that writes ZIP and TAR archives through a streaming API. It is aimed at Node.js services that generate archives on the fly, and version 8.0.0 is ESM-only on Node 18 or later.

**archiverjs/node-archiver** — a streaming interface for archive generation

- Repository: https://github.com/archiverjs/node-archiver
- Website: https://www.archiverjs.com
- Stars: 2,977 · Forks: 249
- Language: JavaScript
- License: MIT
- Published: 2026-09-24 · Updated: 2026-09-24 · Language: en
- Canonical page: https://hysenlabs.com/projects/archiverjs-node-archiver

## What archiver does that a filesystem round trip does not

Most archive code in Node.js follows the same shape: write the files somewhere, shell out or call a library, then read the result back. archiver removes the middle step. It exposes an object that you append entries to and that emits the archive bytes as a readable stream, so the consumer can be an HTTP response, an S3 upload, or a file descriptor. The homepage describes it as "a streaming interface for archive generation", and the README's quick start shows the pattern directly: create an output stream, create the archive, pipe one into the other, append entries, call finalize.

The audience is narrow but real. It fits a backend that serves a download of generated content, a build tool that packages artifacts, or a job that assembles a bundle from buffers and streams it holds in memory. It does not fit a desktop user who wants to right-click a folder. There is no CLI in the repository listing; the top-level entries are index.js, lib/, test/, benchmark/, examples/ and website/, which is a library layout, not an application one.

## The append, pipe, finalize data flow

The mechanism is a writable-side abstraction over zip-stream and tar-stream, both listed as dependencies in package.json, with readable-stream providing the stream implementation and lazystream deferring file reads until the archive actually needs them. That last dependency matters: archive.directory and archive.glob do not read the tree eagerly, so a large directory can be appended without holding every file in memory at once.

Entries enter through several methods, and the README demonstrates each: archive.append with a read stream, a string, or a Buffer; archive.file for a path on disk; archive.directory for a folder, with the second argument either a destination name inside the archive or false to place the contents at the root; and archive.glob for a pattern with a cwd option. All of them accept a name option that sets the entry path inside the archive.

The lifecycle has one trap worth stating plainly. The README's comment says the close event "is fired only when a file descriptor is involved" and warns that close, end or finish may fire right after archive.finalize() is called, so listeners must be registered beforehand. If you attach your completion handler after finalize, you can miss it. The pointer() method reports total bytes written and is only meaningful once writing has finished.

## Installing archiver and producing a first ZIP

The README gives a single install command. It pulls the package from the npm registry and records it in your dependencies.

```bash
npm install archiver --save
```

Because 8.0.0 sets "type": "module" and exports only ./index.js, the import form in the README is the one to use. The example below is the README's quick start reduced to its essential path: open an output stream, create a ZipArchive with a compression level, pipe, append one file from a stream and one from a string, then finalize.

```js
import fs from "fs";
import { ZipArchive } from "archiver";

const output = fs.createWriteStream(__dirname + "/example.zip");
const archive = new ZipArchive({
  zlib: { level: 9 }, // Sets the compression level.
});

archive.on("error", function (err) {
  throw err;
});

archive.pipe(output);
archive.append(fs.createReadStream(__dirname + "/file1.txt"), { name: "file1.txt" });
archive.append("string cheese!", { name: "file2.txt" });
archive.finalize();
```

What you should see is example.zip appear next to the script, containing file1.txt and file2.txt. The README's own version registers a close handler on output and logs archive.pointer() there, which is the reliable place to read the final byte count. It also registers a warning handler and checks err.code === "ENOENT" before rethrowing, because stat failures and similar problems arrive as warnings rather than errors. Copy that handler; without it, a missing file can pass silently into the archive.

## Where archiver is the wrong tool

The README states that archiver ships with "out of the box support for TAR and ZIP archives". That is the whole format list. If you need 7z, RAR, or a ZIP variant with encryption, this library does not offer it, and no plugin mechanism appears in the README or the repository layout. Reaching for archiver there is a dead end.

The second limitation is the module system. Version 8.0.0 declares "type": "module" and "exports": "./index.js". A CommonJS codebase that still calls require("archiver") cannot consume this release without changing how it loads modules or staying on an older line. That is a migration cost, not a bug, but it lands on the consumer.

The third is error handling discipline. Warnings are non-blocking by design, and the README's own example shows an ENOENT branch that only logs. A caller who ignores the warning event can ship an archive that is missing entries and never see an error. If your process cannot tolerate a silently incomplete archive, you have to treat warnings as failures yourself.

Finally, archiver generates archives; it does not read them. package.json lists yauzl under devDependencies, which suggests it is used in tests rather than exposed as an API, and nothing in the README describes extraction.

## How archiver differs from shelling out to tar or zip

The obvious alternative is spawning the system tar or zip binary, or calling the tar package, which appears in archiver's own devDependencies at version 6.2.1. The difference is where the data lives. The system tools and the tar package operate on paths: you hand them a directory or a file list, and they read from the filesystem. archiver operates on streams, so an entry can come from a Buffer that was never written to disk, or from a read stream whose bytes are already in flight.

That distinction decides the choice. If everything you want to archive is already on disk and you control the environment, a shelled-out tar is fewer moving parts and one less dependency tree to audit. If any entry is generated at request time, or if the archive must be streamed to a client before it is complete, archiver's model is the one that fits, and the lazystream dependency exists precisely to keep directory reads deferred.

A second difference is naming control. archive.directory takes a destination name or false for root placement, and archive.glob takes a cwd, so you can reshape the archive layout in code without staging a temporary directory tree that mirrors it.

## Maintenance, licence and upgrade cost

The repository is not archived, and the last push was on 2026-09-23. Release 8.0.0 landed on 2026-05-08, following 7.0.1 in March 2024 and 7.0.0 in February 2024. So the project is being worked on, and the major version is recent enough that the 7.x to 8.x step is the one most teams will face.

The licence is MIT, declared in package.json and present as LICENSE at the repository root. MIT is permissive: it allows commercial and closed-source use with attribution and no warranty. That is a description of the terms, not legal advice; read the LICENSE file in your own context.

The upgrade cost from 7.x is concentrated in the module change. A CommonJS consumer must either convert to ESM or remain on the 7.x line, and the engines field requires Node 18 or later, so older runtimes are out. The dependency set is nine packages, including tar-stream and zip-stream, which means transitive updates arrive through those. renovate.json at the root suggests automated dependency updates are configured, which lowers the cost of keeping current but does not remove the need to test after a bump.

## Conclusion

Adopt archiver if you need to build ZIP or TAR output inside a Node.js process and pipe it somewhere without staging a temporary file: the streaming API is the whole point. Do not adopt it if you are still on CommonJS, since 8.0.0 sets "type": "module" and exports only ./index.js, and do not adopt it if you need a format beyond TAR and ZIP, because the README names only those two. Verify your Node version against the engines field, and check the archive.on("warning") handler for non-blocking stat failures before you rely on the output.

## FAQ

### What is an archiver?

In this context, archiver is a Node.js library that provides a streaming interface for archive generation, with out of the box support for TAR and ZIP. You append entries to an archive object, pipe its output to a destination, and call finalize when done.

### Can I install Node.js using a zip file?

The README documents installing this package with npm install archiver --save, which requires an existing Node.js and npm setup, and package.json requires Node 18 or later. Nothing in the README describes installing Node.js itself from a zip file.

### Is node.js a library?

Node.js is the runtime this package runs on, not the package itself; the engines field in package.json requires Node 18 or later. archiver is the library, and it is published to the npm registry.

### Are node and npm the same thing?

They are not. npm is the package manager used by the README's install command, npm install archiver --save, while Node runs the code, and this package requires Node 18 or later.

## Sources

- [archiverjs/node-archiver on GitHub](https://github.com/archiverjs/node-archiver)
- [License: MIT](https://github.com/archiverjs/node-archiver/blob/master/LICENSE)
- [Project website](https://www.archiverjs.com)
- [README](https://github.com/archiverjs/node-archiver/blob/master/README.md)
- [Releases](https://github.com/archiverjs/node-archiver/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/archiverjs-node-archiver
