mrkeyoor.com_
Sat 08 Aug 21:01 UTC
npmUtilsupdated 08 Aug 2026

@zip.js/zip.js

@zip.js/zip.js is a ZIP archive reader and writer for browsers, Node.js, Deno, Bun, and React Native. It can stream large inputs instead of holding every file in memory, use workers or native compression, create Zip64 and split archives, read Deflate64, and encrypt entries with AES. The API is built around paired Reader and Writer adapters, so a Blob, string, Uint8Array, HTTP resource, or Web Stream can move through the same archive machinery without a separate package for each runtime.

Verdict

The strongest general-purpose ZIP choice for modern JavaScript when streaming, workers, encryption, or large archives are real requirements. For tiny in-memory browser archives, JSZip remains easier, and no ZIP library removes the need to defend extraction paths and resource limits yourself.

API stability4/5The project remains on major version 2 and keeps the core ZipReader, ZipWriter, Reader, and Writer model consistent while adding capabilities through options and adapters. Its export map covers ESM, CommonJS, React Native, core-only, worker, and WASM variants, but that breadth creates more entry-point and asset behavior to track than a small single-runtime library.
Docs4/5The README gives working Blob and Web Stream examples, explains tree shaking and smaller entry points, and links to generated API documentation for every class and option. The type documentation records important details such as forward-slash filenames and safety-check defaults, though discovering a secure production configuration requires assembling facts spread across several option pages.
Maintenance5/5Version 2.8.36 was published on August 8, 2026, the repository was pushed the same day, and the GitHub repository showed one open item, which was a pull request rather than an unresolved issue. Releases and generated type documentation move together, and current runtime declarations explicitly cover Node 18+, Deno, and Bun instead of claiming unsupported legacy compatibility.
Ecosystem4/5The package recorded 4,533,687 downloads for the measured week and the repository has 3,874 stars. It covers browsers, server runtimes, React Native, Web Streams, Blob and typed-array adapters, HTTP range reads, workers, and JSR distribution, but its Reader and Writer abstractions are less commonly recognized than JSZip's simpler object model.

Use it if

  • You need to create or inspect ZIP files in a browser without sending user files to a server
  • You handle archives large enough to need streaming, Zip64, split files, or concurrent compression
  • One codebase must work across modern browsers, Node 18+, Deno, Bun, or React Native
  • You need AES-encrypted ZIP entries, Deflate64 decompression, progress callbacks, or cancellation
Skip it if

Setup reality

Installation is one npm package with no runtime dependencies or peer dependencies, and it ships ESM, CommonJS, and TypeScript declarations. The easy demo uses BlobReader and BlobWriter, but production choices start immediately. Node must be 18 or newer. Browser builds may create Web Workers and use a bundled WASM module; a strict Content Security Policy, CDN deployment, or bundler that relocates assets can require configure() with explicit workerURI and wasmURI values. The smaller lib/zip-core.js entry point omits both assets, which saves code only if you host and configure them yourself. Import named exports because importing the namespace keeps the complete read and write paths. BlobWriter buffers the finished archive in memory, so large files should go through WritableStream adapters or a filesystem-backed temporary stream. Concurrent adds can also create temporary buffers. Always await close(), because it writes the central directory and returns the finished output; an unclosed writer does not produce a valid archive. Entry names must use forward slashes, and backslashes are stored literally rather than normalized. For untrusted archives, inspect names before writing to disk, reject absolute paths and parent traversal, impose your own uncompressed byte and entry-count ceilings, and opt into strictness, signature, ambiguity, and overlap checks where appropriate. Password support does not make filenames private, and compatibility may differ between AES and legacy ZipCrypto readers. React Native uses a separate native export, while Deno documentation uses the JSR package name rather than the npm scope.

Patterns

Create a ZIP containing textcreate-text-archive

import { BlobWriter, TextReader, ZipWriter } from '@zip.js/zip.js';

const writer = new ZipWriter(new BlobWriter('application/zip'));
await writer.add('hello.txt', new TextReader('Hello world'));
const zipBlob = await writer.close();

close() writes the central directory and returns the Blob; always await it before using or downloading the archive.

List files without extracting themlist-archive-entries

import { BlobReader, ZipReader } from '@zip.js/zip.js';

const reader = new ZipReader(new BlobReader(file));
try {
  const entries = await reader.getEntries();
  for (const entry of entries) {
    console.log(entry.filename, entry.directory, entry.uncompressedSize);
  }
} finally {
  await reader.close();
}

Listing reads archive metadata, not file contents; still set application limits on entry count and declared sizes before extraction.

Read one entry as textextract-entry-text

import { BlobReader, TextWriter, ZipReader } from '@zip.js/zip.js';

const reader = new ZipReader(new BlobReader(file));
try {
  const entries = await reader.getEntries();
  const config = entries.find((entry) => entry.filename === 'config.json');
  if (!config || config.directory) throw new Error('config.json is missing');
  const text = await config.getData(new TextWriter());
  console.log(JSON.parse(text));
} finally {
  await reader.close();
}

TextWriter decodes the complete entry into a string, so stream or cap large entries instead of using this pattern blindly.

Extract an entry to a browser Blobextract-entry-blob

import { BlobReader, BlobWriter, ZipReader } from '@zip.js/zip.js';

const reader = new ZipReader(new BlobReader(file));
try {
  const entry = (await reader.getEntries()).find((item) => item.filename === 'photo.png');
  if (!entry || entry.directory) throw new Error('photo.png is missing');
  const blob = await entry.getData(new BlobWriter('image/png'));
  const url = URL.createObjectURL(blob);
  image.src = url;
} finally {
  await reader.close();
}

Revoke the object URL when the preview is no longer needed, and do not trust an archive filename as a MIME type.

Add files selected in a browseradd-browser-files

import { BlobReader, BlobWriter, ZipWriter } from '@zip.js/zip.js';

const writer = new ZipWriter(new BlobWriter('application/zip'));
for (const file of fileInput.files) {
  const safeName = file.name.replaceAll('\\', '_').replaceAll('/', '_');
  await writer.add(safeName, new BlobReader(file), { lastModDate: file.lastModified ? new Date(file.lastModified) : new Date() });
}
const archive = await writer.close();

ZIP entry paths require forward slashes. Flatten user-provided names unless directory structure is intentional and validated.

Write an archive to a Web Streamstream-large-archive

import { BlobReader, ZipWriter } from '@zip.js/zip.js';

const stream = new TransformStream();
const response = new Response(stream.readable, {
  headers: { 'Content-Type': 'application/zip' },
});
const writer = new ZipWriter(stream.writable);
const writing = (async () => {
  await writer.add('video.mp4', new BlobReader(videoFile));
  await writer.close();
})();
await Promise.all([writing, consumeResponse(response)]);

The readable side must be consumed while writing or backpressure can stall the writer; BlobWriter is simpler but retains the output in memory.

Track compression and extraction progresstrack-progress

await zipWriter.add('data.bin', sourceReader, {
  onprogress: (loaded, total) => {
    if (total) progress.value = loaded / total;
  },
});

await entry.getData(destinationWriter, {
  onprogress: (loaded, total) => {
    console.log(`${loaded} of ${total} bytes`);
  },
});

Treat total as potentially unavailable or zero for streaming sources whose size is not known in advance.

Cancel compression with AbortControllercancel-operation

const controller = new AbortController();
cancelButton.addEventListener('click', () => controller.abort());

try {
  await zipWriter.add('large.bin', sourceReader, { signal: controller.signal });
} catch (error) {
  if (error.name !== 'AbortError') throw error;
}

An aborted add can leave a partially written entry; close or discard the output according to the writer state instead of presenting it as complete.

Create an AES-encrypted entryencrypt-entry-aes

const writer = new ZipWriter(new BlobWriter('application/zip'));
await writer.add('private.txt', new TextReader(secret), {
  password,
  encryptionStrength: 3,
});
const encryptedZip = await writer.close();

Strength 3 selects AES-256. ZIP metadata such as filenames remains visible, and recipients need a ZIP tool that supports AES encryption.

Read a password-protected entrydecrypt-entry

const reader = new ZipReader(new BlobReader(encryptedFile));
try {
  const entry = (await reader.getEntries()).find((item) => !item.directory);
  if (!entry) throw new Error('archive has no file entry');
  const value = await entry.getData(new TextWriter(), {
    password,
    checkSignature: true,
  });
  console.log(value);
} finally {
  await reader.close();
}

A wrong password rejects during extraction. Avoid logging the password or accepting unlimited attempts in a public service.

Apply stricter checks to untrusted entriesinspect-untrusted-archive

const reader = new ZipReader(new BlobReader(upload), { strictness: 'strict' });
try {
  for (const entry of await reader.getEntries()) {
    const name = entry.filename.replaceAll('\\', '/');
    if (name.startsWith('/') || name.split('/').includes('..')) {
      throw new Error(`unsafe entry path: ${name}`);
    }
    if (!entry.directory) {
      await entry.getData(destinationFor(name), {
        strictness: 'strict',
        checkSignature: true,
        checkOverlappingEntry: true,
      });
    }
  }
} finally {
  await reader.close();
}

These checks do not impose an uncompressed-size ceiling or prove that destinationFor stays inside a chosen root; enforce both separately.

Configure worker and WASM asset URLsconfigure-worker-assets

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

configure({
  workerURI: new URL('./zip-web-worker.js', import.meta.url).href,
  wasmURI: new URL('./zip-module.wasm', import.meta.url).href,
  maxWorkers: 2,
});

Use this when a CDN, CSP, or core-only entry point prevents the default embedded assets from loading; copy the matching distribution files during the build.

Alternatives

PackageRegistryPick it when
jszipnpmSmall browser archives where a simple in-memory object API matters more than streaming and advanced ZIP features
fflatenpmYou want a compact, fast compression library and can work with a lower-level synchronous, asynchronous, or streaming API
client-zipnpmYou only need to generate ZIP downloads in modern browsers from streams, blobs, or fetched responses