JavaScript library for compressing and decompressing ZIP files in browsers, Node.js, and Deno with support for encryption, ZIP64, and web workers
Reading and extracting data from ZIP files with support for various formats, encryption, and streaming.
The main class for reading ZIP files.
class ZipReader<Type> {
constructor(
reader: Reader<Type> | ReadableReader | ReadableStream | (Reader<unknown> | ReadableReader | ReadableStream)[],
options?: ZipReaderConstructorOptions
);
readonly comment: Uint8Array;
readonly prependedData?: Uint8Array;
readonly appendedData?: Uint8Array;
getEntries(options?: ZipReaderGetEntriesOptions): Promise<Entry[]>;
getEntriesGenerator(options?: ZipReaderGetEntriesOptions): AsyncGenerator<Entry, boolean>;
close(): Promise<void>;
}reader: Data source for the ZIP file. Can be a Reader instance, ReadableStream, or array for split archivesoptions: Configuration options for reading behaviorcomment: Global comment of the ZIP file as Uint8ArrayprependedData: Data that appears before the ZIP file (if extractPrependedData option is true)appendedData: Data that appears after the ZIP file (if extractAppendedData option is true)Returns all entries in the ZIP file as an array.
const entries = await zipReader.getEntries();
for (const entry of entries) {
console.log(entry.filename, entry.uncompressedSize);
}Returns an async generator for iterating through entries, useful for large ZIP files to avoid loading all entries into memory.
for await (const entry of zipReader.getEntriesGenerator()) {
console.log(entry.filename);
if (someCondition) break; // Can exit early
}Closes the ZipReader and releases resources.
await zipReader.close();ZIP entries are either files or directories.
type Entry = DirectoryEntry | FileEntry;interface DirectoryEntry extends EntryMetaData {
directory: true;
getData?: undefined;
}Directory entries represent folders in the ZIP archive. They have all the metadata but no getData method.
interface FileEntry extends EntryMetaData {
directory: false;
getData<Type>(
writer: Writer<Type> | WritableWriter | WritableStream | AsyncGenerator<Writer<unknown> | WritableWriter | WritableStream, boolean>,
options?: EntryGetDataOptions
): Promise<Type>;
arrayBuffer(options?: EntryGetDataOptions): Promise<ArrayBuffer>;
}File entries represent actual files with content that can be extracted.
Extracts the file content using the specified writer.
// Extract as text
const text = await fileEntry.getData(new TextWriter());
// Extract as Blob
const blob = await fileEntry.getData(new BlobWriter());
// Extract as Uint8Array
const data = await fileEntry.getData(new Uint8ArrayWriter());
// Extract to multiple split files
const splitWriter = new SplitDataWriter(async function* () {
yield new BlobWriter();
yield new BlobWriter();
return true;
}, 1024 * 1024); // 1MB chunks
await fileEntry.getData(splitWriter);Convenience method to extract file content as ArrayBuffer.
const buffer = await fileEntry.arrayBuffer();
const uint8Array = new Uint8Array(buffer);All entries (files and directories) contain extensive metadata.
interface EntryMetaData {
// Location and identification
offset: number;
filename: string;
rawFilename: Uint8Array;
filenameUTF8: boolean;
// Entry type and permissions
directory: boolean;
executable: boolean;
// Encryption status
encrypted: boolean;
zipCrypto: boolean;
// Size information
compressedSize: number;
uncompressedSize: number;
// Timestamps
lastModDate: Date;
lastAccessDate?: Date;
creationDate?: Date;
rawLastModDate: number | bigint;
rawLastAccessDate?: number | bigint;
rawCreationDate?: number | bigint;
// Comments
comment: string;
rawComment: Uint8Array;
commentUTF8: boolean;
// Integrity and format
signature: number;
extraField?: Map<number, { type: number; data: Uint8Array }>;
rawExtraField: Uint8Array;
zip64: boolean;
version: number;
versionMadeBy: number;
// File attributes
msDosCompatible: boolean;
internalFileAttributes: number;
externalFileAttributes: number;
diskNumberStart: number;
compressionMethod: number;
}filename: Entry name as decoded stringdirectory: true for directories, false for filesencrypted: true if entry is password protectedcompressedSize / uncompressedSize: Size in byteslastModDate: Last modification date as Date objectsignature: CRC32 checksum for integrity verificationzip64: true if entry uses ZIP64 format (for large files)interface ZipReaderConstructorOptions extends ZipReaderOptions, GetEntriesOptions, WorkerConfiguration {
extractPrependedData?: boolean;
extractAppendedData?: boolean;
}extractPrependedData: Extract data before ZIP into prependedData propertyextractAppendedData: Extract data after ZIP into appendedData propertyinterface ZipReaderOptions {
checkPasswordOnly?: boolean;
checkSignature?: boolean;
checkOverlappingEntry?: boolean;
checkOverlappingEntryOnly?: boolean;
password?: string;
passThrough?: boolean;
rawPassword?: Uint8Array;
signal?: AbortSignal;
preventClose?: boolean;
transferStreams?: boolean;
}password: Password for encrypted entriescheckSignature: Verify CRC32 signatures (slower but more secure)checkOverlappingEntry: Detect overlapping entries (for malformed ZIPs)signal: AbortSignal to cancel operationspassThrough: Read data as-is without decompression/decryptioninterface GetEntriesOptions {
filenameEncoding?: string;
commentEncoding?: string;
decodeText?(value: Uint8Array, encoding: string): string | undefined;
}filenameEncoding: Encoding for filenames (e.g., "cp437", "utf8")commentEncoding: Encoding for commentsdecodeText: Custom text decoder functioninterface EntryGetDataOptions extends EntryDataOnprogressOptions, ZipReaderOptions, WorkerConfiguration {}Options for extracting individual entry data, combining progress tracking, reading options, and worker configuration.
import { ZipReader, BlobReader, TextWriter } from "@zip.js/zip.js";
const zipReader = new ZipReader(new BlobReader(zipBlob));
const entries = await zipReader.getEntries();
// Find a specific file
const textFile = entries.find(entry => entry.filename === "readme.txt");
if (textFile && !textFile.directory) {
const text = await textFile.getData(new TextWriter());
console.log(text);
}
await zipReader.close();const zipReader = new ZipReader(new BlobReader(zipBlob), {
password: "secret123"
});
const entries = await zipReader.getEntries();
for (const entry of entries) {
if (!entry.directory && entry.encrypted) {
const data = await entry.getData(new Uint8ArrayWriter(), {
password: "secret123"
});
console.log(`Extracted ${entry.filename}: ${data.length} bytes`);
}
}
await zipReader.close();const zipReader = new ZipReader(new BlobReader(zipBlob));
// Progress during entry enumeration
const entries = await zipReader.getEntries({
onprogress: (progress, total, entry) => {
console.log(`Reading entry ${progress}/${total}: ${entry.filename}`);
}
});
// Progress during data extraction
for (const entry of entries) {
if (!entry.directory) {
const data = await entry.getData(new Uint8ArrayWriter(), {
onstart: (total) => console.log(`Starting extraction of ${total} bytes`),
onprogress: (progress, total) => {
console.log(`Progress: ${Math.round(progress/total*100)}%`);
},
onend: (computedSize) => console.log(`Completed: ${computedSize} bytes`)
});
}
}
await zipReader.close();// For split ZIP files (.z01, .z02, .zip)
const readers = [
new BlobReader(part1Blob), // .z01
new BlobReader(part2Blob), // .z02
new BlobReader(finalBlob) // .zip
];
const zipReader = new ZipReader(readers);
const entries = await zipReader.getEntries();
// Process entries normally
await zipReader.close();import { HttpRangeReader } from "@zip.js/zip.js";
// Efficient for large remote ZIP files - only downloads needed parts
const zipReader = new ZipReader(
new HttpRangeReader("https://example.com/large-archive.zip")
);
const entries = await zipReader.getEntries();
// Only the central directory and specific file data is downloaded
await zipReader.close();// Use generator for large ZIP files to avoid loading all entries at once
const zipReader = new ZipReader(new BlobReader(zipBlob));
for await (const entry of zipReader.getEntriesGenerator()) {
if (entry.filename.endsWith('.txt') && !entry.directory) {
const text = await entry.getData(new TextWriter());
console.log(`${entry.filename}: ${text.substring(0, 100)}...`);
// Can break early to save memory
if (text.includes('target-content')) {
break;
}
}
}
await zipReader.close();Common errors when reading ZIP files:
import {
ERR_BAD_FORMAT,
ERR_EOCDR_NOT_FOUND,
ERR_ENCRYPTED,
ERR_INVALID_PASSWORD,
ERR_UNSUPPORTED_COMPRESSION
} from "@zip.js/zip.js";
try {
const zipReader = new ZipReader(new BlobReader(zipBlob));
const entries = await zipReader.getEntries();
for (const entry of entries) {
if (!entry.directory) {
try {
const data = await entry.getData(new Uint8ArrayWriter());
} catch (error) {
if (error.message === ERR_INVALID_PASSWORD) {
console.log(`Wrong password for ${entry.filename}`);
} else if (error.message === ERR_UNSUPPORTED_COMPRESSION) {
console.log(`Unsupported compression method in ${entry.filename}`);
} else {
console.error(`Error extracting ${entry.filename}:`, error);
}
}
}
}
await zipReader.close();
} catch (error) {
if (error.message === ERR_BAD_FORMAT) {
console.error("Invalid ZIP file format");
} else if (error.message === ERR_EOCDR_NOT_FOUND) {
console.error("ZIP file appears to be truncated or corrupted");
} else {
console.error("Error reading ZIP file:", error);
}
}Install with Tessl CLI
npx tessl i tessl/npm-zip-js--zip-js