How to download an S3 folder as a zip file

TL;DR

S3 has no folder-download endpoint and no server-side zip — a folder is just a shared key prefix, and every object under it has to be fetched individually. The AWS CLI's aws s3 sync pulls the whole tree to disk; S3 Viewer's Download as zip instead streams each object straight into a zip archive and sends the finished file to your browser, without ever holding the whole thing in memory. It's capped by object count and total size so a request can't quietly try to zip an entire bucket.

Steps

Step-by-step.

  1. 01

    In S3 Viewer: Download as zip

    Choose Download as zip from a folder row's ⋮ menu, or select several files and folders and use it from the selection toolbar. S3 Viewer lists everything under the prefix(es), checks the total count and size are within limits, and then starts the download — the browser shows the usual save-file prompt once the archive is ready.
  2. 02

    AWS CLI: sync the folder to disk, then zip it locally

    aws s3 sync mirrors the prefix to a local directory; zip it yourself once it's down. This downloads every object individually to your machine before anything is archived.
    aws s3 sync s3://my-bucket/reports/2026/ ./2026
    cd .. && zip -r 2026.zip 2026
  3. 03

    SDK: list, get each object, stream into an archive

    Any zip library that accepts a stream can be fed one GetObject body at a time as you page through ListObjectsV2 — this is the same shape S3 Viewer's server-side archive route runs, minus the size and count checks.
    const archive = archiver('zip', { zlib: { level: 0 } });
    archive.pipe(outputStream);
    
    for await (const key of listAllKeys(bucket, prefix)) {
      const { Body } = await s3.getObject({ Bucket: bucket, Key: key });
      archive.append(Body, { name: key.slice(prefix.length) });
    }
    await archive.finalize();
  4. 04

    Why S3 Viewer stores rather than compresses

    The archive is built with zip's store method (no compression), since most object storage already holds compressed formats (images, video, PDFs) where re-compressing wastes CPU for negligible size savings. The trade-off is a larger zip than a deflate-compressed one would produce for genuinely compressible content, in exchange for a much faster archive.
  5. 05

    Limits exist so a zip can't quietly become a whole bucket

    A folder with more objects than MAX_ARCHIVE_OBJECTS (10,000 by default) or more total bytes than MAX_ARCHIVE_BYTES (10 GiB by default) is refused up front, before any object is read, with a message naming which limit was hit. An operator self-hosting S3 Viewer can raise both via environment variables.
Under the hood

What's actually happening.

A prefix isn't a container S3 can hand you as one unit — it's a filter over a flat list of keys. Turning it into a single downloadable file means listing every key under it, reading each object's body, and writing all of them into an archive format, which is exactly what S3 Viewer's/v3/archive route does: ListObjectsV2 to expand any folder keys recursively, then one GetObject per file streamed straight into a zip being written to the response — nothing is buffered on disk or held fully in memory.


Because building the archive can take a while and touches many objects, the route checks the total object count and byte size against configured limits before it starts streaming anything, and returns a short-lived, single-use download token rather than the file itself — the actual archive is fetched with a second request against that token, which is what lets the browser show a normal save dialog instead of the page navigating away mid-download.


The archive uses zip's store method — no deflate compression — partly for speed and partly because most object storage already holds pre-compressed formats. zip64 is enabled so the format itself has no 4 GiB or 65,535-file ceiling; the configured MAX_ARCHIVE_BYTES and MAX_ARCHIVE_OBJECTS are what actually bound a request, and both exist to keep one download from turning into an unbounded read of an entire bucket.

FAQ

Common questions.

Can I download an entire S3 folder as one zip file?

Not with a single native S3 call — there's no folder-download or server-side-zip API. The AWS console downloads a folder as separate files or, in some regions, offers a batch download that still fetches objects individually behind the scenes. S3 Viewer's Download as zip button does the equivalent from the browser: it lists everything under the prefix, streams each object into an archive, and sends you one file, bounded by an object-count and total-size limit.

Does S3 have a native zip download feature?

No. S3 stores objects, not archives, and has no endpoint that returns a compressed bundle of a prefix. Every zip-a-folder feature — the AWS console's, a third-party tool's, or S3 Viewer's — builds the archive itself by reading the individual objects.

How do I download a folder from S3 using the AWS CLI?

aws s3 sync s3://bucket/prefix/ ./local-folder mirrors the prefix to disk as individual files, preserving the relative paths. Zip it afterward with your OS's zip tool if you want a single file; the CLI itself has no zip option.

Is there a size limit on zip downloads in S3 Viewer?

Yes, two: MAX_ARCHIVE_OBJECTS (default 10,000 objects) and MAX_ARCHIVE_BYTES (default 10 GiB total, checked against the objects' listed sizes before anything downloads). Both are checked up front and both are configurable by whoever self-hosts the instance. A folder over either limit is refused with a message naming which one.

What happens if an object fails to download partway through the zip?

The whole archive is aborted rather than finished with a gap — S3 Viewer ends the response without writing a valid zip central directory, so the download comes through as a truncated, unopenable file instead of a zip that looks complete but is silently missing an object.

Does the zip download work the same way on R2, B2 and MinIO?

Yes. Building the archive only needs ListObjectsV2 and GetObject, which every S3-compatible provider implements the same way, so the zip feature has no provider-specific behavior at all.
Use S3 Viewer for this

Skip the CLI. Try it in the browser.

S3 Viewer turns the steps above into a single click. Open source, self-hostable, free.