Eazip
Guides

Download Google Cloud Storage Files as a ZIP

List a bucket prefix, sign V4 read URLs, and package the objects into one ZIP with @eazip/core in the browser.

Cloud Storage has no built-in "download folder as ZIP" action. List the objects under a prefix, sign a V4 read URL for each one, and hand the resulting { url, filename } list to @eazip/core. Storage credentials stay on the server; the browser, or Eazip Cloud for larger exports, does the archiving.

Loading live demo…

Prepare V4 signed URLs on the server

Your backend endpoint should:

  1. authenticate the application user;
  2. list the objects under the requested bucket and prefix;
  3. authorize the request against those specific objects; and
  4. return short-lived signed URLs, never a service account key.
server/export.ts
import { Storage } from '@google-cloud/storage';

const storage = new Storage();

type SignedSource = {
  url: string;
  filename: string;
};

export async function listPrefixAsSignedUrls(
  bucketName: string,
  prefix: string,
): Promise<SignedSource[]> {
  const [objects] = await storage.bucket(bucketName).getFiles({ prefix });

  return Promise.all(
    objects
      .filter((file) => !file.name.endsWith('/'))
      .map(async (file) => {
        const [url] = await file.getSignedUrl({
          version: 'v4',
          action: 'read',
          expires: Date.now() + 15 * 60 * 1000,
        });

        return { url, filename: file.name.slice(prefix.length) };
      }),
  );
}

getFiles({ prefix }) auto-paginates and returns every matching object in one array, so there is no continuation-token loop to write. Keep expires short, but long enough for every file to finish downloading; a URL that expires mid-fetch fails only that one file, and the archive is still usable if you leave failOnUrlError at its default.

Configure bucket CORS

The browser fetches each object directly from Cloud Storage, so the bucket must allow your application's origin:

gcs-cors.json
[
  {
    "origin": ["https://app.example.com"],
    "method": ["GET"],
    "responseHeader": ["Content-Type"],
    "maxAgeSeconds": 3600
  }
]

Apply it with the gcloud CLI. The file passed to --cors-file is a bare array, with no top-level "cors" key:

gcloud storage buckets update gs://your-bucket --cors-file=gcs-cors.json

Without this, the browser blocks the fetch and Eazip reports the file as failed even though the signed URL itself is valid. CORS changes can take a few minutes to take effect.

Create the ZIP in the browser

download-export.ts
import { createZip } from '@eazip/core';

const response = await fetch('/api/exports/bucket-prefix', {
  credentials: 'include',
});

if (!response.ok) {
  throw new Error('Could not prepare export files');
}

const files: { url: string; filename: string }[] = await response.json();

files also accepts plain URL strings, or a mix of URLs and browser File or Blob objects if part of the export comes from a local picker; see Input types for every accepted shape.

const result = await createZip({
  files,
  zipName: 'bucket-export.zip',
});

result.download();

createZip() resolves once every reachable object has been fetched and packaged, then result.download() starts the browser download. Use startZip() instead when you need live progress or a cancel button; see Create a ZIP from remote URLs for that shape and for how partial results behave when some objects fail.

When to use Eazip Cloud

Local jobs like the one above run entirely in the visitor's tab. Switch to Eazip Cloud when:

  • the prefix totals multiple gigabytes or thousands of objects;
  • the bucket cannot allow browser CORS, for example a locked-down internal bucket;
  • the export should survive a page reload; or
  • you would rather not hold archive bytes in tab memory at all.

Cloud accepts the same signed URL list; only the strategy changes:

const result = await createZip({
  strategy: 'cloud',
  publicKey: 'pk_ez_...',
  files,
  zipName: 'bucket-export.zip',
});

result.downloadAll();

Add storage.googleapis.com, or the bucket's custom domain, to the Public App's allowed source hosts, and give the signed URLs enough lifetime for Eazip Cloud to start and finish reading each object. Stream mode is the default and keeps archive bytes out of tab memory without producing a reusable file; switch to stored mode when the same export should be downloadable more than once. See When to use Eazip Cloud for the full comparison with Local.

Limits and alternatives

Every approach here still pays Cloud Storage's standard network egress pricing: objects served to a browser fetch, or to Eazip Cloud, count as data leaving the bucket. "Zero-egress" on Eazip's side means Eazip does not add its own outbound-bandwidth fee on top of that; it does not remove Cloud Storage's own charge for the source fetches.

Eazip is not the right tool for every Cloud Storage download:

  • A single object doesn't need a ZIP at all; return one signed read URL and let the browser download it directly.
  • Internal users with gcloud access can skip signed URLs entirely and run gcloud storage cp -r gs://your-bucket/prefix . (or the older gsutil -m cp -r) to copy a prefix locally in parallel.
  • A server-side archive, built with a Cloud Run job or Cloud Function that streams objects into a ZIP and uploads the result, avoids sending bytes through the visitor's browser, but you take on that function's own memory and timeout limits. Eazip Cloud exists largely so you don't have to build and operate that path yourself.

FAQ

Can I download a Cloud Storage folder as a ZIP without a server?

Not safely for private data. A "folder" in Cloud Storage is just a shared object name prefix, and listing or reading it requires a service account or a signed URL, so some backend step has to authenticate the request and sign the objects. If the prefix is fully public, you can generate the URL list once and cache it, but the listing step still needs to run somewhere with access to the bucket.

Does Google Cloud Storage support ZIP downloads natively?

No. Cloud Storage stores and serves individual objects; it has no operation that returns a prefix as a single archive. The Cloud Console can download one object at a time, but not a folder. Eazip does the archiving in the browser or in Eazip Cloud instead.

Why does my Cloud Storage ZIP download fail with a CORS error?

The browser is fetching each signed URL directly from Cloud Storage, and the bucket has no CORS rule allowing your origin. Add the CORS configuration shown above, and confirm method includes GET for the app's exact origin.

How large a bucket can Eazip zip?

A Local job is limited by the browser tab's memory and the tab staying open. For anything multi-gigabyte, thousands of objects, or that needs to survive a reload, use strategy: 'cloud' as shown above.

Do signed URLs expose my Google Cloud credentials?

No. A V4 signed URL grants time-limited read access to one object using your service account's key to sign it; the key itself never reaches the browser. Keep expires as short as your download flow allows.

Continue with When to use Eazip Cloud, Input types, and Create a ZIP from remote URLs.