JavaScript library for compressing and decompressing ZIP files in browsers, Node.js, and Deno with support for encryption, ZIP64, and web workers
High-level filesystem-like operations for complex ZIP manipulation, providing an intuitive interface for managing ZIP archives as virtual filesystems.
The main entry point for the filesystem API.
const fs: {
FS: typeof FS;
ZipDirectoryEntry: typeof ZipDirectoryEntry;
ZipFileEntry: typeof ZipFileEntry;
};import { fs } from "@zip.js/zip.js";
// Create a new filesystem
const zipFs = new fs.FS();
// Add files and directories
zipFs.addText("readme.txt", "Welcome!");
const assetsDir = zipFs.addDirectory("assets");
assetsDir.addBlob("logo.png", logoBlob);
// Export as ZIP
const zipBlob = await zipFs.exportBlob();The root filesystem class that manages ZIP entries as a virtual filesystem.
class FS extends ZipDirectoryEntry {
readonly root: ZipDirectoryEntry;
remove(entry: ZipEntry): void;
move(entry: ZipEntry, destination: ZipDirectoryEntry): void;
find(fullname: string): ZipEntry | undefined;
getById(id: number): ZipEntry | undefined;
}root: The root directory entryRemoves an entry and all its children from the filesystem.
const fileEntry = zipFs.addText("temp.txt", "temporary");
zipFs.remove(fileEntry); // File is removed from filesystemMoves an entry to a different directory.
const file = zipFs.addText("misplaced.txt", "content");
const targetDir = zipFs.addDirectory("correct-location");
zipFs.move(file, targetDir); // Now at "correct-location/misplaced.txt"Finds an entry by its full path.
const entry = zipFs.find("assets/images/logo.png");
if (entry && !entry.directory) {
const blob = await entry.getBlob();
}Finds an entry by its unique ID.
const entry = zipFs.getById(123);
if (entry) {
console.log(`Found: ${entry.getFullname()}`);
}Base class for all filesystem entries.
class ZipEntry {
name: string;
data?: EntryMetaData;
id: number;
parent?: ZipEntry;
uncompressedSize: number;
children: ZipEntry[];
clone(deepClone?: boolean): ZipEntry;
getFullname(): string;
getRelativeName(ancestor: ZipDirectoryEntry): string;
isDescendantOf(ancestor: ZipDirectoryEntry): boolean;
isPasswordProtected(): boolean;
checkPassword(password: string, options?: EntryGetDataOptions): Promise<boolean>;
rename(name: string): void;
}name: Entry name (without path)data: Associated metadata from ZIP fileid: Unique identifierparent: Parent directoryuncompressedSize: Size of uncompressed contentchildren: Child entries (for directories)Returns the complete path of the entry.
const file = assetsDir.addText("style.css", "body {}");
console.log(file.getFullname()); // "assets/style.css"Returns the path relative to an ancestor directory.
const deepFile = zipFs.find("assets/images/icons/home.png");
const assetsDir = zipFs.find("assets");
console.log(deepFile.getRelativeName(assetsDir)); // "images/icons/home.png"Checks if the entry or any of its children requires a password.
if (entry.isPasswordProtected()) {
const isValid = await entry.checkPassword("secret123");
if (isValid) {
// Password is correct
}
}Changes the name of the entry.
const file = zipFs.addText("old-name.txt", "content");
file.rename("new-name.txt");
console.log(file.getFullname()); // "new-name.txt"Represents a file in the virtual filesystem.
class ZipFileEntry<ReaderType, WriterType> extends ZipEntry {
directory: void;
reader: Reader<ReaderType> | ReadableReader | ReadableStream | (Reader<unknown> | ReadableReader | ReadableStream)[];
writer: Writer<WriterType> | WritableWriter | WritableStream | AsyncGenerator<Writer<unknown> | WritableWriter | WritableStream>;
getText(encoding?: string, options?: EntryGetDataOptions): Promise<string>;
getBlob(mimeType?: string, options?: EntryGetDataOptions): Promise<Blob>;
getData64URI(mimeType?: string, options?: EntryGetDataOptions): Promise<string>;
getUint8Array(options?: EntryGetDataOptions): Promise<Uint8Array>;
getWritable(writable?: WritableStream, options?: EntryGetDataOptions): Promise<WritableStream>;
getData<Type>(writer: Writer<unknown> | WritableWriter | WritableStream | AsyncGenerator<Writer<unknown> | WritableWriter | WritableStream>, options?: EntryGetDataOptions): Promise<Type>;
getArrayBuffer(options?: EntryGetDataOptions): Promise<ArrayBuffer>;
replaceBlob(blob: Blob): void;
replaceText(text: string): void;
replaceData64URI(dataURI: string): void;
replaceUint8Array(array: Uint8Array): void;
replaceReadable(readable: ReadableStream): void;
}Retrieves file content as text.
const textFile = zipFs.addText("config.json", '{"key": "value"}');
const content = await textFile.getText();
const config = JSON.parse(content);Retrieves file content as a Blob.
const imageFile = zipFs.addBlob("photo.jpg", photoBlob);
const retrievedBlob = await imageFile.getBlob("image/jpeg");Retrieves file content as a Base64 Data URI.
const iconFile = zipFs.addBlob("icon.png", iconBlob);
const dataUri = await iconFile.getData64URI("image/png");
// Result: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA..."Retrieves file content as raw bytes.
const binaryFile = zipFs.addBlob("data.bin", binaryBlob);
const bytes = await binaryFile.getUint8Array();
console.log(`File size: ${bytes.length} bytes`);Replaces file content with new text.
const configFile = zipFs.addText("config.json", '{"version": 1}');
configFile.replaceText('{"version": 2}');Replaces file content with a new Blob.
const imageFile = zipFs.addBlob("old-image.png", oldBlob);
imageFile.replaceBlob(newImageBlob);Represents a directory in the virtual filesystem.
class ZipDirectoryEntry extends ZipEntry {
directory: true;
getChildByName(name: string): ZipEntry | undefined;
// Adding entries
addDirectory(name: string, options?: ZipWriterAddDataOptions): ZipDirectoryEntry;
addText(name: string, text: string, options?: ZipWriterAddDataOptions): ZipFileEntry<string, string>;
addBlob(name: string, blob: Blob, options?: ZipWriterAddDataOptions): ZipFileEntry<Blob, Blob>;
addData64URI(name: string, dataURI: string, options?: ZipWriterAddDataOptions): ZipFileEntry<string, string>;
addUint8Array(name: string, array: Uint8Array, options?: ZipWriterAddDataOptions): ZipFileEntry<Uint8Array, Uint8Array>;
addHttpContent(name: string, url: string, options?: HttpOptions & ZipWriterAddDataOptions): ZipFileEntry<string, void>;
addReadable(name: string, readable: ReadableStream, options?: ZipWriterAddDataOptions): ZipFileEntry<ReadableStream, void>;
addFile(file: File, options?: ZipWriterAddDataOptions): Promise<ZipEntry>;
addFileSystemEntry(fileSystemEntry: FileSystemEntryLike, options?: ZipWriterAddDataOptions): Promise<ZipEntry[]>;
addFileSystemHandle(fileSystemHandle: FileSystemHandleLike, options?: ZipWriterAddDataOptions): Promise<ZipEntry[]>;
// Importing ZIP files
importBlob(blob: Blob, options?: ZipReaderConstructorOptions): Promise<[ZipEntry]>;
importData64URI(dataURI: string, options?: ZipReaderConstructorOptions): Promise<[ZipEntry]>;
importUint8Array(array: Uint8Array, options?: ZipReaderConstructorOptions): Promise<[ZipEntry]>;
importHttpContent(url: string, options?: ZipDirectoryEntryImportHttpOptions): Promise<[ZipEntry]>;
importReadable(readable: ReadableStream, options?: ZipReaderConstructorOptions): Promise<[ZipEntry]>;
importZip(reader: Reader<unknown> | ReadableReader | ReadableStream | (Reader<unknown> | ReadableReader | ReadableStream)[], options?: ZipReaderConstructorOptions): Promise<[ZipEntry]>;
// Exporting
exportBlob(options?: ZipDirectoryEntryExportOptions): Promise<Blob>;
exportData64URI(options?: ZipDirectoryEntryExportOptions): Promise<string>;
exportUint8Array(options?: ZipDirectoryEntryExportOptions): Promise<Uint8Array>;
exportWritable(writable?: WritableStream, options?: ZipDirectoryEntryExportOptions): Promise<WritableStream>;
exportZip(writer: Writer<unknown> | WritableWriter | WritableStream | AsyncGenerator<Writer<unknown> | WritableWriter | WritableStream>, options?: ZipDirectoryEntryExportOptions): Promise<unknown>;
}Finds a direct child by name.
const assetsDir = zipFs.addDirectory("assets");
assetsDir.addText("style.css", "body {}");
const cssFile = assetsDir.getChildByName("style.css");
if (cssFile && !cssFile.directory) {
const content = await cssFile.getText();
}Creates a new subdirectory.
const projectDir = zipFs.addDirectory("my-project");
const srcDir = projectDir.addDirectory("src");
const testDir = projectDir.addDirectory("test");Adds a text file.
const srcDir = zipFs.addDirectory("src");
const mainFile = srcDir.addText("main.js", `
console.log("Hello, World!");
export default function main() {
// Application logic
}
`);Adds a binary file from a Blob.
const assetsDir = zipFs.addDirectory("assets");
const logoFile = assetsDir.addBlob("logo.png", logoBlob, {
lastModDate: new Date(),
comment: "Company logo"
});Adds content fetched from a URL.
const libsDir = zipFs.addDirectory("libs");
const jqueryFile = libsDir.addHttpContent(
"jquery.min.js",
"https://code.jquery.com/jquery-3.6.0.min.js",
{
headers: [["User-Agent", "MyApp/1.0"]]
}
);Adds content from a File object (from file input or drag & drop).
// From file input
const fileInput = document.querySelector('input[type="file"]');
for (const file of fileInput.files) {
await zipFs.addFile(file);
}Extracts a ZIP file into the directory.
const backupDir = zipFs.addDirectory("backup");
await backupDir.importBlob(existingZipBlob);
// All entries from existingZipBlob are now in backup/Downloads and extracts a ZIP file from a URL.
const vendorDir = zipFs.addDirectory("vendor");
await vendorDir.importHttpContent(
"https://example.com/library-v1.2.3.zip"
);Exports the directory and its contents as a ZIP Blob.
const projectDir = zipFs.addDirectory("my-project");
projectDir.addText("README.md", "# My Project");
projectDir.addText("package.json", '{"name": "my-project"}');
const zipBlob = await projectDir.exportBlob({
level: 9, // Maximum compression
mimeType: "application/zip"
});Exports as a Base64 Data URI.
const dataUri = await zipFs.exportData64URI();
// Can be used directly in download links
const downloadLink = document.createElement('a');
downloadLink.href = dataUri;
downloadLink.download = 'archive.zip';import { fs } from "@zip.js/zip.js";
async function createProjectArchive() {
const zipFs = new fs.FS();
// Create project structure
const projectDir = zipFs.addDirectory("my-web-app");
const srcDir = projectDir.addDirectory("src");
const assetsDir = projectDir.addDirectory("assets");
const docsDir = projectDir.addDirectory("docs");
// Add source files
srcDir.addText("index.html", `
<!DOCTYPE html>
<html>
<head><title>My Web App</title></head>
<body><h1>Hello World</h1></body>
</html>
`);
srcDir.addText("app.js", `
document.addEventListener('DOMContentLoaded', () => {
console.log('App loaded');
});
`);
srcDir.addText("style.css", `
body { font-family: Arial, sans-serif; }
h1 { color: #333; }
`);
// Add assets
await assetsDir.addHttpContent(
"jquery.min.js",
"https://code.jquery.com/jquery-3.6.0.min.js"
);
// Add documentation
docsDir.addText("README.md", `
# My Web App
A simple web application.
## Getting Started
Open index.html in a web browser.
`);
docsDir.addText("API.md", `
# API Documentation
## Functions
- \`init()\` - Initializes the application
`);
// Add project metadata
projectDir.addText("package.json", JSON.stringify({
name: "my-web-app",
version: "1.0.0",
description: "A simple web application",
main: "src/index.html"
}, null, 2));
// Export with compression
const zipBlob = await projectDir.exportBlob({
level: 6,
comment: "Project created on " + new Date().toISOString()
});
return zipBlob;
}import { fs } from "@zip.js/zip.js";
class ArchiveManager {
constructor() {
this.zipFs = new fs.FS();
}
async loadArchive(zipBlob) {
await this.zipFs.importBlob(zipBlob);
}
addFolder(path) {
const parts = path.split('/');
let currentDir = this.zipFs;
for (const part of parts) {
let child = currentDir.getChildByName(part);
if (!child) {
child = currentDir.addDirectory(part);
} else if (!child.directory) {
throw new Error(`Path conflict: ${part} is a file`);
}
currentDir = child;
}
return currentDir;
}
addTextFile(path, content) {
const parts = path.split('/');
const filename = parts.pop();
const dirPath = parts.join('/');
const dir = dirPath ? this.addFolder(dirPath) : this.zipFs;
return dir.addText(filename, content);
}
async addBinaryFile(path, blob) {
const parts = path.split('/');
const filename = parts.pop();
const dirPath = parts.join('/');
const dir = dirPath ? this.addFolder(dirPath) : this.zipFs;
return dir.addBlob(filename, blob);
}
findFile(path) {
return this.zipFs.find(path);
}
listFiles(directory = "") {
const dir = directory ? this.zipFs.find(directory) : this.zipFs;
if (!dir || !dir.directory) return [];
return dir.children.map(child => ({
name: child.name,
path: child.getFullname(),
isDirectory: child.directory,
size: child.uncompressedSize
}));
}
async moveFile(fromPath, toPath) {
const file = this.zipFs.find(fromPath);
if (!file) throw new Error(`File not found: ${fromPath}`);
const parts = toPath.split('/');
const filename = parts.pop();
const dirPath = parts.join('/');
const targetDir = dirPath ? this.addFolder(dirPath) : this.zipFs;
file.rename(filename);
this.zipFs.move(file, targetDir);
}
deleteFile(path) {
const file = this.zipFs.find(path);
if (file) {
this.zipFs.remove(file);
return true;
}
return false;
}
async exportArchive(options = {}) {
return await this.zipFs.exportBlob({
level: 6,
...options
});
}
async exportDirectory(path, options = {}) {
const dir = this.zipFs.find(path);
if (!dir || !dir.directory) {
throw new Error(`Directory not found: ${path}`);
}
return await dir.exportBlob(options);
}
}
// Usage
const manager = new ArchiveManager();
// Add files
manager.addTextFile("config/app.json", '{"debug": true}');
manager.addTextFile("src/main.js", "console.log('Hello');");
await manager.addBinaryFile("assets/logo.png", logoBlob);
// List contents
console.log(manager.listFiles()); // Root files
console.log(manager.listFiles("src")); // Files in src/
// Move file
await manager.moveFile("config/app.json", "config/production.json");
// Export specific directory
const srcZip = await manager.exportDirectory("src");
// Export entire archive
const fullZip = await manager.exportArchive({
level: 9,
comment: "Managed archive"
});import { fs } from "@zip.js/zip.js";
class IncrementalArchiveBuilder {
constructor() {
this.zipFs = new fs.FS();
this.changes = new Set();
}
addContent(path, content, options = {}) {
const parts = path.split('/');
const filename = parts.pop();
let currentDir = this.zipFs;
for (const part of parts) {
let child = currentDir.getChildByName(part);
if (!child) {
child = currentDir.addDirectory(part);
}
currentDir = child;
}
let entry;
if (typeof content === 'string') {
entry = currentDir.addText(filename, content, options);
} else if (content instanceof Blob) {
entry = currentDir.addBlob(filename, content, options);
} else if (content instanceof Uint8Array) {
entry = currentDir.addUint8Array(filename, content, options);
}
this.changes.add(path);
return entry;
}
updateContent(path, newContent) {
const entry = this.zipFs.find(path);
if (!entry || entry.directory) {
throw new Error(`File not found: ${path}`);
}
if (typeof newContent === 'string') {
entry.replaceText(newContent);
} else if (newContent instanceof Blob) {
entry.replaceBlob(newContent);
} else if (newContent instanceof Uint8Array) {
entry.replaceUint8Array(newContent);
}
this.changes.add(path);
}
getChangedFiles() {
return Array.from(this.changes);
}
async createSnapshot() {
const snapshot = await this.zipFs.exportBlob({
level: 6,
comment: `Snapshot created ${new Date().toISOString()}`
});
this.changes.clear();
return snapshot;
}
async createDelta(basePath = null) {
if (this.changes.size === 0) return null;
const deltaFs = new fs.FS();
for (const changedPath of this.changes) {
const entry = this.zipFs.find(changedPath);
if (entry && !entry.directory) {
const data = await entry.getData(new Uint8ArrayWriter());
deltaFs.addUint8Array(changedPath, data);
}
}
return await deltaFs.exportBlob({
level: 9,
comment: `Delta with ${this.changes.size} changes`
});
}
}
// Usage
const builder = new IncrementalArchiveBuilder();
// Build archive incrementally
builder.addContent("v1/app.js", "console.log('v1');");
builder.addContent("v1/config.json", '{"version": 1}');
const v1Snapshot = await builder.createSnapshot();
// Make changes
builder.updateContent("v1/app.js", "console.log('v1.1');");
builder.addContent("v1/patch.js", "console.log('patch');");
const delta = await builder.createDelta();
const v1_1Snapshot = await builder.createSnapshot();
console.log("Changed files:", builder.getChangedFiles());The Filesystem API provides a powerful and intuitive way to work with ZIP files as virtual filesystems, making complex archive operations simple and maintainable.
Install with Tessl CLI
npx tessl i tessl/npm-zip-js--zip-js