zlib.createZipArchive(entries, options?): stream.Readable
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.
Iterable | AsyncIterableZipEntry.{ comment: options }.stringnumberbaseOffset bytes already written to
the same file, rather than at its start. Default: 0.stream.ReadableSerializes 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);