On this page

    M

    zlib.createZipArchive

    History
    zlib.createZipArchive(entries, options?): stream.Readable
    Stability: 1.0Early development

    The ZIP archive API is experimental. Using any part of it (this function among them) emits an experimental warning the first time; merely importing node:zlib does not.

    Attributes
    options:string | Object
    An archive comment, as a shorthand for { comment: options }.
    comment:string
    An archive comment.
    baseOffset?:number
    Shifts every local/central header offset the archive records by this many bytes, so the emitted stream is self-describing even when something else is written before it - for example, appending the archive after baseOffset bytes already written to the same file, rather than at its start. Default: 0.
    A byte stream of the serialized archive.

    Serializes entries into a ZIP archive, switching to Zip64 structures automatically once the entry count, or any offset or size, exceeds what the classic 32-/16-bit ZIP fields can hold. The returned Readable is also an AsyncIterable of the same Buffer chunks it streams.

    Entries are written in iteration order and nothing deduplicates names: an iterable that yields two entries with the same name produces an archive containing both, and most extraction tools keep the one that appears later. ZipBuffer and ZipFile add() methods replace entries by name instead.

    The entries are owned by the returned stream: each is consumed as the archive is produced and must not be reused afterwards. This matters for streaming entries (from zlib.ZipEntry.createStream()), which hold an underlying source such as a file read stream. If the returned stream is destroyed before it is fully consumed - for example, the destination of a pipeline() fails - it disposes the entry it was serializing and every entry still queued behind it, destroying their sources so no descriptor leaks. Consume the stream to the end, or destroy it (directly, through a failed pipeline(), or with await using), to guarantee this cleanup; a stream that is neither consumed nor destroyed cannot release anything. A ZipEntry that is never handed to an archive can be released directly with Symbol.dispose / Symbol.asyncDispose.

    Throws an ERR_ZIP_ARCHIVE_TOO_LARGE error if the archive comment exceeds 65,535 bytes when encoded as UTF-8.

    import { createWriteStream } from 'node:fs';
    import { pipeline } from 'node:stream/promises';
    import { Buffer } from 'node:buffer';
    import { ZipEntry, createZipArchive } from 'node:zlib';
    
    const entries = [
      await ZipEntry.create('hello.txt', Buffer.from('Hello, world!')),
      await ZipEntry.create('data/', Buffer.alloc(0)),
    ];
    await pipeline(
      createZipArchive(entries, 'created by node:zlib'),
      createWriteStream('archive.zip'),
    );
    const { createWriteStream } = require('node:fs');
    const { pipeline } = require('node:stream/promises');
    const { ZipEntry, createZipArchive } = require('node:zlib');
    
    async function main() {
      const entries = [
        await ZipEntry.create('hello.txt', Buffer.from('Hello, world!')),
        await ZipEntry.create('data/', Buffer.alloc(0)),
      ];
      await pipeline(
        createZipArchive(entries, 'created by node:zlib'),
        createWriteStream('archive.zip'),
      );
    }
    main();

    Passing options.baseOffset produces an archive that is valid immediately when placed after other content in the same file, without relying on a reader's self-extracting-archive detection to compensate for the shift:

    import { createWriteStream } from 'node:fs';
    import { Buffer } from 'node:buffer';
    import { ZipEntry, createZipArchive } from 'node:zlib';
    
    const prefix = Buffer.from('#!/bin/sh\nexit 0\n');
    const entries = [await ZipEntry.create('hello.txt', Buffer.from('Hello, world!'))];
    const out = createWriteStream('self-extracting.zip');
    out.write(prefix);
    createZipArchive(entries, { baseOffset: prefix.byteLength }).pipe(out);