Library / SDK
gildas-lormeau/zip.js avatar
gildas-lormeau/zip.js

zip.js: a JavaScript zip library built around web streams and workers

Project brief: JavaScript library to zip and unzip files supporting parallel compression, web streams, zip64, split files, data encryption, and deflate64 decompression.

3,893 stars547 forksJavaScriptBSD-3-Clause

At a glance

What is it?
zip.js is a BSD-3-Clause JavaScript library for reading and writing zip archives in the browser, Deno and Node.js. It is aimed at large files and streamed data, and its cost is a worker-based architecture you have to understand before you ship it.
Who is it for?
Adopt zip.js when you need to read or write zip data inside a browser or a stream pipeline, when archives may exceed 4GB, or when you need AES encryption or split volumes. Do not adopt it for a build script that shells out to the system zip binary, or if you want a dependency with no worker and no bundler configuration.
Can I use it commercially?
Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 2 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 17, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What zip.js solves, and who ends up using it

Browser JavaScript has no built-in zip API. If a page needs to hand a user a folder as a single archive, or read an archive the user dropped onto it, the code has to either build the container format by hand or pull in a library that does. zip.js is that library. Its README describes it as having been "designed to handle large amounts of data", and the feature list backs the claim: multi-core compression, native compression with compression streams, pluggable compression engines, archives larger than 4GB via Zip64, split zip files, data encryption, incremental writing and Deflate64 decompression.

The audience is narrower than "anyone who needs zip". The package.json description names three targets: the browser, Deno and Node.js. That is a deliberate position. Code that runs in all three cannot depend on Node's zlib bindings, so zip.js reaches for platform primitives that exist in all of them: Web Workers, the Web Streams API, CompressionStream and Web Crypto. The engines field states the floors: Node 18.0.0 or newer, Deno 1.0.0 or newer, Bun 0.7.0 or newer.

A second group of users is less obvious. The keyword list includes usdz, the packaging format used for AR content on Apple platforms. USDZ files are zip containers, so anyone writing tooling that inspects or produces them ends up needing a zip reader that works in a browser tab rather than on a server. That use case explains why Deflate64 decompression is in the list: the library has to read archives it did not create.

Readers, writers and the worker boundary

The architecture separates where bytes come from, where they go, and who does the compression. Reader classes wrap a source: BlobReader for a File or Blob, TextReader for a string, HttpReader for a URL. Writer classes wrap a destination: BlobWriter collects the result into a Blob, TextWriter returns a string. ZipWriter.add takes an entry name and a reader; ZipReader.getEntries returns the entries and each entry exposes its own extraction method against a writer.

Compression itself runs off the main thread. The README shows that zip.js lets you create its web workers yourself using the new Worker(new URL("./zip-worker.js", import.meta.url)) form, and notes that bundlers like Vite, webpack and Rollup detect that pattern and bundle the worker automatically. This is the part that decides whether zip.js is pleasant or painful in your project. The worker is a real runtime dependency, not an implementation detail, and the library exposes it precisely so that bundlers can follow it.

The engine is also replaceable. A configuration entry point at @zip.js/zip.js/lib/zip-core-custom.js accepts a configure call where you supply your own createWorker, and the worker file calls initWorker with a CompressionStreamFallback and a DecompressionStreamFallback. That means the Deflate implementation is not hard-wired: the README names fflate as an example engine you can delegate to. For a library that advertises native compression via CompressionStream, having a documented escape hatch matters, because CompressionStream is not available everywhere.

Installing zip.js and writing a first archive

The package is published as @zip.js/zip.js on npm. The README's import examples use that specifier, with a comment that Deno users should prefix it with jsr: as jsr:@zip-js/zip-js. Install it with your package manager of choice:

bash
npm install @zip.js/zip.js

The Hello world example builds an archive in memory. A BlobWriter is the destination, a TextReader supplies the entry content, and ZipWriter.close resolves to the finished zip as a Blob:

js
import {
  BlobReader,
  BlobWriter,
  TextReader,
  TextWriter,
  ZipReader,
  ZipWriter
} from "@zip.js/zip.js";

const zipFileWriter = new BlobWriter();
const helloWorldReader = new TextReader("Hello world!");

The README also shows a stream variant. A TransformStream is created, the zip content is written into its writable property, and a Response constructed from the readable property turns the stream back into a Blob:

js
import { BlobReader, ZipReader, ZipWriter } from "@zip-js/zip-js";

const zipFileStream = new TransformStream();
const zipFileBlobPromise = new Response(zipFileStream.readable).blob();

For concurrent entries, the README's example passes several add calls to Promise.all. One entry comes from a TextReader, another from an HttpReader pointed at a URL. The documentation states that the entries are added concurrently, which is the behaviour the multi-core claim rests on. Read the full examples on the project's documentation site before adapting them; the README snippets are abridged.

Where zip.js is the wrong tool

The worker requirement is the first thing that can go wrong. If your build pipeline does not recognise the new Worker(new URL(...)) pattern, the worker will not be emitted and the library will fail at runtime rather than at build time. The README names Vite, webpack and Rollup as bundlers that detect the form, but it does not provide a fallback recipe for older or more unusual build setups. That is a real constraint, not a footnote.

Second, the API is asynchronous and stream-oriented throughout. If your code path is synchronous, or if you are processing a small archive once at page load, the machinery costs more than it returns. A synchronous zip library, or letting the server produce the archive, will be simpler.

Third, zip.js is a library, not a command. There is no CLI in the repository layout or the README. If your actual need is to compress a directory in a shell script, you want the system zip binary and zip.js is irrelevant.

Finally, consider what you are unpacking. The library will faithfully extract whatever the archive contains, including entries with path traversal in their names. The README does not document any sanitisation of entry names on extraction, so path handling is the caller's job. If you accept archives from untrusted users, that check belongs in your code, not in the library.

zip.js compared with JSZip and fflate

JSZip is the library most people reach for first, and it is the comparison that shows up in search. The difference in approach is architectural. JSZip is a pure JavaScript implementation with a synchronous, promise-based API that runs on the main thread. zip.js pushes compression into Web Workers and builds on platform primitives: CompressionStream, Web Streams and Web Crypto. That is why zip.js can describe multi-core compression and native compression as features, and why it can offer a stream interface where data is written as it arrives rather than buffered into a single in-memory structure.

The trade is complexity. A worker boundary means bundler configuration, a message-passing model, and a harder time in environments where workers are restricted. JSZip has none of that.

fflate appears in the README in two roles, which is worth noticing. It is named as an example of a custom compression engine you can plug into zip.js via CompressionStreamFallback and DecompressionStreamFallback. It is also a standalone compression library in its own right. So the choice is not strictly either-or: if you want zip.js's container handling and streaming model but a different Deflate implementation, the configure hook exists for exactly that. If you only need to inflate or deflate a buffer, you do not need the zip container at all.

Maintenance, releases and licence terms

The repository is not archived, and the last push was on 2026-08-28. The release list shows v2.8.61 on 2026-08-28, v2.8.60 on 2026-08-25 and v2.8.59 on 2026-08-24: three patch releases inside five days. Note that package.json reports version 2.15.0 while the release tags read v2.8.x, and the version script runs sync-versions.js across package.json, deno.json and lib/core/version.js. Read the changelog for the version you actually install rather than assuming the two numbering schemes line up.

Upgrade cost is shaped by the API surface. The test script enumerates checks for API symmetry, API shape, API mangling and API exports, alongside fidelity, types and web-streams-polyfill runs. A project that tests its own API shape that aggressively is signalling that the surface is meant to be stable and that changes to it are treated as breaking. For consumers, that is a reason to keep the dependency current rather than pinned far behind: patch releases land often, and the test suite is the safety net.

The licence is BSD-3-Clause, stated in both the README and package.json. That is a permissive licence: it allows use in closed-source products provided the copyright notice and licence text are retained. It is not a copyleft licence, so it does not require you to publish your own source. This is a description of the licence text, not legal advice; if your organisation has a licence review process, run the package through it.

Editorial conclusion

Adopt zip.js when you need to read or write zip data inside a browser or a stream pipeline, when archives may exceed 4GB, or when you need AES encryption or split volumes. Do not adopt it for a build script that shells out to the system zip binary, or if you want a dependency with no worker and no bundler configuration. Before committing, verify that your bundler picks up the new Worker(new URL(...)) form, confirm your runtime meets the declared engines floor of Node 18, Deno 1.0.0 or Bun 0.7.0, and check the tests/all directory for a case that matches your target runtime.

Frequently asked questions

How do you create a zip file in JavaScript with zip.js?

Create a BlobWriter as the destination and a reader such as TextReader or HttpReader for the content, then call ZipWriter.add for each entry and await ZipWriter.close, which resolves to the finished archive as a Blob. The README's Hello world example shows the full sequence.

Is extracting a zip file safe?

zip.js extracts the entries an archive contains, and the README does not document any sanitisation of entry names, so a crafted archive can carry names that escape the intended directory when you write them out. Validate entry names in your own code before writing extracted data to a filesystem.

How does zip.js compare with fflate?

They operate at different levels. fflate is a compression library, and the zip.js README names it as an example of a custom engine you can plug in through CompressionStreamFallback and DecompressionStreamFallback. zip.js supplies the zip container, the reader and writer classes, and the worker-based execution model around that engine.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Community notes

Community notes