JavaScript library for compressing and decompressing ZIP files in browsers, Node.js, and Deno with support for encryption, ZIP64, and web workers
Creating and modifying ZIP files with support for compression, encryption, and various data sources.
The main class for creating ZIP files.
class ZipWriter<Type> {
constructor(
writer: Writer<Type> | WritableWriter | WritableStream | AsyncGenerator<Writer<unknown> | WritableWriter | WritableStream, boolean>,
options?: ZipWriterConstructorOptions
);
readonly hasCorruptedEntries?: boolean;
prependZip<ReaderType>(
reader: Reader<ReaderType> | ReadableReader | ReadableStream | (Reader<unknown> | ReadableReader | ReadableStream)[]
): Promise<void>;
add<ReaderType>(
filename: string,
reader?: Reader<ReaderType> | ReadableReader | ReadableStream | (Reader<unknown> | ReadableReader | ReadableStream)[],
options?: ZipWriterAddDataOptions
): Promise<EntryMetaData>;
remove(entry: Entry | string): boolean;
close(comment?: Uint8Array, options?: ZipWriterCloseOptions): Promise<Type>;
}writer: Destination for the ZIP file. Can be a Writer instance, WritableStream, or generator for split archivesoptions: Configuration options for writing behaviorhasCorruptedEntries: true if any entries were partially written (indicates corruption)Adds a new entry to the ZIP file.
// Add text file
await zipWriter.add("hello.txt", new TextReader("Hello World!"));
// Add binary file
await zipWriter.add("image.png", new BlobReader(imageBlob));
// Add directory
await zipWriter.add("folder/", null, { directory: true });
// Add with options
await zipWriter.add("secret.txt", new TextReader("Secret data"), {
password: "secret123",
level: 9,
lastModDate: new Date()
});Adds an existing ZIP file at the beginning of the current ZIP. Must be called before any add() calls.
const existingZipReader = new ZipReader(new BlobReader(existingZipBlob));
await zipWriter.prependZip(existingZipReader);
// Now add new entries
await zipWriter.add("new-file.txt", new TextReader("New content"));Marks an entry for removal (entry won't be written to the final ZIP).
const entryMetadata = await zipWriter.add("temp.txt", new TextReader("temp"));
const removed = zipWriter.remove(entryMetadata); // or remove("temp.txt")
console.log(removed); // true if entry was removedFinalizes the ZIP file by writing the central directory and closing the writer.
const zipBlob = await zipWriter.close();
// Optional global comment
const zipBlobWithComment = await zipWriter.close(
new TextEncoder().encode("Created by my app")
);interface ZipWriterConstructorOptions {
zip64?: boolean;
preventClose?: boolean;
level?: number;
bufferedWrite?: boolean;
keepOrder?: boolean;
password?: string;
rawPassword?: Uint8Array;
encryptionStrength?: 1 | 2 | 3;
signal?: AbortSignal;
lastModDate?: Date;
lastAccessDate?: Date;
creationDate?: Date;
extendedTimestamp?: boolean;
zipCrypto?: boolean;
version?: number;
versionMadeBy?: number;
useUnicodeFileNames?: boolean;
dataDescriptor?: boolean;
dataDescriptorSignature?: boolean;
msDosCompatible?: boolean;
externalFileAttributes?: number;
internalFileAttributes?: number;
supportZip64SplitFile?: boolean;
usdz?: boolean;
passThrough?: boolean;
encrypted?: boolean;
offset?: number;
compressionMethod?: number;
encodeText?(text: string): Uint8Array | undefined;
}level: Compression level 0-9 (0=no compression, 9=maximum compression, default: 6)password: Default password for encrypting entriesencryptionStrength: AES encryption strength (1=128-bit, 2=192-bit, 3=256-bit, default: 3)zip64: Force ZIP64 format for large files (>4GB) or many entries (>65535)keepOrder: Maintain entry order in the file (improves web worker performance)bufferedWrite: Buffer entry data before writing (required for some scenarios)useUnicodeFileNames: Mark filenames as UTF-8 encodedlastModDate: Default last modification date for entriesextendedTimestamp: Store extended timestamp information (access/creation dates)zipCrypto: Use legacy ZipCrypto encryption (not recommended, use AES instead)usdz: Create USDZ-compatible ZIP filesinterface ZipWriterAddDataOptions extends ZipWriterConstructorOptions, EntryDataOnprogressOptions, WorkerConfiguration {
directory?: boolean;
executable?: boolean;
comment?: string;
extraField?: Map<number, Uint8Array>;
uncompressedSize?: number;
signature?: number;
}Entry-specific options that override constructor defaults:
directory: true to create a directory entryexecutable: true to mark file as executablecomment: Entry-specific commentextraField: Custom extra field datauncompressedSize: Specify size if reader doesn't provide itsignature: CRC32 signature if known in advanceinterface ZipWriterCloseOptions extends EntryOnprogressOptions {
zip64?: boolean;
preventClose?: boolean;
}zip64: Force ZIP64 format for central directorypreventClose: Don't close the underlying writer streamimport { ZipWriter, BlobWriter, TextReader, BlobReader } from "@zip.js/zip.js";
const zipWriter = new ZipWriter(new BlobWriter("application/zip"));
// Add text file
await zipWriter.add("readme.txt", new TextReader("Welcome to my app!"));
// Add binary file
await zipWriter.add("logo.png", new BlobReader(logoBlob));
// Add directory
await zipWriter.add("assets/", null, { directory: true });
await zipWriter.add("assets/style.css", new TextReader("body { margin: 0; }"));
// Finalize ZIP
const zipBlob = await zipWriter.close();const zipWriter = new ZipWriter(new BlobWriter("application/zip"), {
password: "secret123",
encryptionStrength: 3 // 256-bit AES
});
await zipWriter.add("confidential.txt", new TextReader("Top secret data"));
// Override password for specific entry
await zipWriter.add("public.txt", new TextReader("Public data"), {
password: undefined // No encryption for this entry
});
const encryptedZip = await zipWriter.close();const zipWriter = new ZipWriter(new BlobWriter("application/zip"), {
level: 9, // Maximum compression
keepOrder: true // Better performance with web workers
});
// Add large text files that compress well
await zipWriter.add("large-log.txt", new BlobReader(logFileBlob));
await zipWriter.add("data.json", new TextReader(JSON.stringify(largeDataObject)));
const compressedZip = await zipWriter.close();const zipWriter = new ZipWriter(new BlobWriter("application/zip"), {
extendedTimestamp: true,
useUnicodeFileNames: true
});
const customDate = new Date('2023-01-01T12:00:00Z');
await zipWriter.add("document.txt", new TextReader("Content"), {
lastModDate: customDate,
lastAccessDate: customDate,
creationDate: customDate,
comment: "Important document",
executable: false
});
const zipWithMetadata = await zipWriter.close(
new TextEncoder().encode("Archive created on " + new Date().toISOString())
);// Create split ZIP files (useful for size limits)
async function* createSplitWriters() {
let partNumber = 1;
while (partNumber < 10) { // Max 10 parts
const writer = new BlobWriter("application/zip");
writer.maxSize = 1024 * 1024; // 1MB per part
yield writer;
partNumber++;
}
return true; // Signal completion
}
const zipWriter = new ZipWriter(createSplitWriters());
// Add files normally - they'll be split automatically
await zipWriter.add("large-file1.dat", new BlobReader(largeBlob1));
await zipWriter.add("large-file2.dat", new BlobReader(largeBlob2));
await zipWriter.close();const zipWriter = new ZipWriter(new BlobWriter("application/zip"));
await zipWriter.add("large-file.bin", new BlobReader(largeBlob), {
onstart: (total) => {
console.log(`Starting compression of ${total} bytes`);
},
onprogress: (progress, total) => {
const percent = Math.round(progress / total * 100);
console.log(`Compression progress: ${percent}%`);
},
onend: (computedSize) => {
console.log(`Compression completed: ${computedSize} bytes processed`);
}
});
const zipBlob = await zipWriter.close({
onprogress: (progress, total, entry) => {
console.log(`Writing central directory: entry ${progress}/${total} (${entry.filename})`);
}
});import { HttpReader } from "@zip.js/zip.js";
const zipWriter = new ZipWriter(new BlobWriter("application/zip"));
// Add file directly from URL without downloading to memory first
await zipWriter.add(
"remote-file.pdf",
new HttpReader("https://example.com/document.pdf")
);
await zipWriter.add(
"another-remote-file.jpg",
new HttpReader("https://example.com/image.jpg", {
headers: [["Authorization", "Bearer token123"]]
})
);
const zipBlob = await zipWriter.close();// Read existing ZIP
const existingZipReader = new ZipReader(new BlobReader(existingZipBlob));
const existingEntries = await existingZipReader.getEntries();
// Create new ZIP with modifications
const zipWriter = new ZipWriter(new BlobWriter("application/zip"));
// Copy existing entries (except ones to modify)
for (const entry of existingEntries) {
if (entry.filename !== "file-to-replace.txt") {
await zipWriter.add(entry.filename,
entry.directory ? null : entry.getData(new Uint8ArrayReader(await entry.getData(new Uint8ArrayWriter())))
);
}
}
// Add new/replacement entries
await zipWriter.add("file-to-replace.txt", new TextReader("New content"));
await zipWriter.add("new-file.txt", new TextReader("Additional content"));
await existingZipReader.close();
const modifiedZip = await zipWriter.close();// Create USDZ file (ZIP-based format for 3D assets)
const zipWriter = new ZipWriter(new BlobWriter("model/vnd.usdz+zip"), {
usdz: true, // USDZ-specific formatting
level: 0, // USDZ typically uses no compression
useUnicodeFileNames: false
});
await zipWriter.add("scene.usdc", new BlobReader(sceneBlob));
await zipWriter.add("textures/diffuse.jpg", new BlobReader(textureBlob));
const usdzBlob = await zipWriter.close();// Optimize for large ZIP creation
const zipWriter = new ZipWriter(new BlobWriter("application/zip"), {
level: 6, // Balanced compression
keepOrder: true, // Better web worker utilization
bufferedWrite: false, // Stream directly for better memory usage
useWebWorkers: true, // Enable parallel compression
useCompressionStream: true // Use native compression if available
});
// Add files in order of increasing size for better parallelization
const files = [smallFile1, smallFile2, mediumFile1, largeFile1];
for (const file of files) {
await zipWriter.add(file.name, new BlobReader(file.blob));
}
const optimizedZip = await zipWriter.close();import {
ERR_DUPLICATED_NAME,
ERR_INVALID_ENTRY_NAME,
ERR_ZIP_NOT_EMPTY
} from "@zip.js/zip.js";
try {
const zipWriter = new ZipWriter(new BlobWriter("application/zip"));
await zipWriter.add("file.txt", new TextReader("content"));
// This will throw ERR_DUPLICATED_NAME
await zipWriter.add("file.txt", new TextReader("different content"));
} catch (error) {
if (error.message === ERR_DUPLICATED_NAME) {
console.error("Duplicate filename in ZIP");
} else if (error.message === ERR_INVALID_ENTRY_NAME) {
console.error("Invalid entry name");
} else {
console.error("Error creating ZIP:", error);
}
}// Use custom compression codec
import { configure } from "@zip.js/zip.js";
configure({
Deflate: CustomDeflateClass,
Inflate: CustomInflateClass
});
const zipWriter = new ZipWriter(new BlobWriter("application/zip"));
// ZIP will use custom compressionconfigure({
workerScripts: {
deflate: ["./custom-deflate-worker.js"],
inflate: ["./custom-inflate-worker.js"]
}
});// For very large ZIP files, use streaming and limit workers
configure({
maxWorkers: 2, // Limit concurrent workers
terminateWorkerTimeout: 1000 // Terminate workers quickly
});
const zipWriter = new ZipWriter(new BlobWriter("application/zip"), {
bufferedWrite: false, // Stream directly to reduce memory usage
keepOrder: false // Allow out-of-order writing for memory efficiency
});Install with Tessl CLI
npx tessl i tessl/npm-zip-js--zip-js