zip.js

A JavaScript library to zip and unzip files

Notable features

This library depends on the Promise, the TypedArray, the Streams APIs and these ones optionally:

Compatibility

This library works fully with the latest versions of Chrome, Firefox, Safari, Microsoft Edge, Node.js, Deno, and Bun. The minimum supported versions depend on the imported build, cf. the Library variants and Bundle size sections:

Build Chrome, Edge Firefox Safari Node.js Deno Bun
WebAssembly builds: index.js (default), lib/zip-core-wasm.js, dist/zip.min.js, dist/zip-fs.min.js 76 102 15.1 18 1.0 0.7
"native" JavaScript builds: index-native.js, lib/zip-core-native.js, dist/zip-native.min.js, dist/zip-fs-native.min.js 76 102 14.1 18 1.0 0.7
"core" builds: lib/zip-core.js, lib/zip-core-reader.js, lib/zip-core-writer.js, lib/zip-core-custom.js, dist/zip-core.min.js, dist/zip-fs-core.min.js 80 113 16.4 18 1.19 1.3.3

Reading untrusted zip files

zip.js validates untrusted input by default. The filenames returned by getEntries() are checked against path traversal: names containing .. segments, absolute paths, drive letters or UNC prefixes are rejected with an ERR_UNSAFE_FILENAME error. The data of an entry cannot be read outside the bounds of the zip file. Optional checks harden reading further: the checkOverlappingEntry option detects entries sharing bytes with other entries, the checkCrc32 option verifies the CRC-32 checksum of the extracted data, and strictness: "strict" detects duplicate filenames, ambiguous end of central directory records, and local file headers contradicting the central directory. The filename validation can be relaxed or disabled with the filenameValidation option; an application resolving paths from entry.filename, e.g. to write extracted files to disk, relies on it staying enabled.

Some checks remain the responsibility of the application:

Demos

Documentation

Installation

Warning: Make sure that the code is parsed in UTF-8, e.g. served with the HTTP header "Content-Type: application/javascript; charset=utf-8", otherwise filenames might be corrupted when reading zip files.

You can get the code via:

Note: import * as zip imports the whole library. See the Bundle size section below to reduce the footprint of zip.js in your application.

You can also install manually the library by adding zip.min.js from the /dist directory in your project. Then, include zip.min.jsin your HTML page:

Bundle size

zip.js is tree-shakeable: import named identifiers, e.g. import { ZipReader, BlobReader, TextWriter } from "@zip.js/zip.js";, and your bundler removes what you do not use, e.g. the whole write path if you only read zip files.

Smaller entry points are also available, e.g. @zip.js/zip.js/lib/zip-core.js embeds neither the Web Worker code nor the WebAssembly module, pass workerURI and wasmURI to configure() instead. @zip.js/zip.js/lib/zip-core-custom.js goes further and embeds no deflate implementation at all, cf. Custom web workers and compression engines. The prebuilt bundles in /dist are standalone files for script tags and AMD loaders, they are not tree-shaken.

The @zip.js/zip.js/external entry point offers the full API but references the Web Worker code and the WebAssembly module as external files instead of embedding them. Bundlers like webpack and Vite detect these references, emit zip-web-worker.js and zip-module.wasm as separate assets, and remove approximately 45KB of embedded payloads from the main bundle. The worker also runs from a real file URL. This avoids blob: restrictions on pages and browser extensions with a strict Content Security Policy. With Vite, add @zip.js/zip.js to optimizeDeps.exclude to resolve the assets during development. With bundlers which do not rewrite new URL(..., import.meta.url) expressions, copy the two files next to the output bundle. The prebuilt bundle zip-fs-external.min.js offers the same behavior without a bundler, it resolves the two files from its own directory: deploy the three files together.

Smaller compositions of this entry point are also available: @zip.js/zip.js/lib/zip-fs-core-external.js excludes the MIME type table (approximately 23KB), getMimeType() then returns "application/octet-stream", and @zip.js/zip.js/lib/zip-core-external.js also excludes the filesystem API. Both come with prebuilt ES module bundles in /dist. The full MIME type table remains available from any entry point via @zip.js/zip.js/mime-types.

Library variants

The library comes in two variants. The default variant embeds a WebAssembly build of zlib, while the "native" variant embeds a JavaScript port of zlib instead. This embedded code is used when compressing data with a non-default level, when decompressing Deflate64 entries, when the useCompressionStream option is set to false, or when the Compression Streams API is unavailable. Otherwise, both variants rely on the Compression Streams API and behave identically.

zip.js uses WebAssembly by default for more flexibility: the embedded zlib module works inside a web worker or in the main thread, and the same binary is shared between both contexts without duplicating any code. This also makes the default variant smaller. The "native" variant does not depend on WebAssembly, e.g. for React Native or pages with a Content Security Policy which does not allow wasm-unsafe-eval. React Native resolves the main entry point of the package to the "native" variant automatically.

You can import the "native" variant:

The smaller entry points also have a "native" twin, e.g. @zip.js/zip.js/lib/zip-core-native.js instead of @zip.js/zip.js/lib/zip-core.js. The "native" variant is not available on JSR.

Custom web workers and compression engines

The createWorker option of configure() lets you create the web workers yourself, with the standard pattern below that bundlers like webpack and Vite detect statically to compile the worker script and its imports into a separate asset. It takes precedence over workerURI.

import { configure } from "@zip.js/zip.js";

configure({
  createWorker: () => new Worker(new URL("./zip-worker.js", import.meta.url), { type: "module" })
});

A custom worker script calls initWorker(), exposed by the @zip.js/zip.js/worker entry point. It can register an alternative deflate implementation: classes implementing the CompressionStream and DecompressionStream interfaces with the "deflate-raw" format, e.g. based on fflate. See the complete example in the API documentation of initWorker().

import { initWorker } from "@zip.js/zip.js/worker";
import { CompressionStreamFallback, DecompressionStreamFallback } from "./fflate-streams.js";

initWorker({ CompressionStreamFallback, DecompressionStreamFallback });

The @zip.js/zip.js/lib/zip-core-custom.js entry point is designed for this use case: it offers the full API but embeds neither the web worker code nor any deflate implementation. Combined with createWorker, the compression engine of your choice then ships only once, in the worker script. In a test with fflate, the application bundle and the worker script weighed approximately 115KB in total, 43KB gzipped, versus approximately 158KB, 72KB gzipped, for the default entry point.

import { configure } from "@zip.js/zip.js/lib/zip-core-custom.js";

configure({
  createWorker: () => new Worker(new URL("./zip-worker.js", import.meta.url), { type: "module" })
});

The same classes can also be registered in the main thread via the CompressionStreamFallback and DecompressionStreamFallback options of configure(), e.g. when useWebWorkers is set to false. Like the embedded implementation they replace, they are used when useCompressionStream is set to false or when the Compression Streams API is unavailable. These options were previously named CompressionStreamZlib and DecompressionStreamZlib, the old names are deprecated but remain supported. The registered implementation also determines the support of custom compression level values and, via the "deflate64-raw" format, of Deflate64 decompression.

Build

The online builder generates a customized standalone build of zip.js directly in the browser, from the latest version published on npm. You can restrict the API to reading or writing zip files, include the web workers, choose the embedded compression implementation (WebAssembly, JavaScript, or none to rely on the Compression Streams API only), include the filesystem API, and download the result as a minified UMD or ES module file.

You can also build the library from the source code:

$ git clone https://github.com/gildas-lormeau/zip.js.git
$ cd zip.js
$ npm ci
$ npm run build

The bundles are generated in the /dist directory. npm test runs the test suite with Node.js, Deno and Bun, and npm run test-browsers runs it in Chrome, Firefox and Safari. See the /tests directory for more details.