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:
- authenticate the application user;
- list the objects under the requested bucket and prefix;
- authorize the request against those specific objects; and
- return short-lived signed URLs, never a service account key.
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:
[
{
"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.jsonWithout 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
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
gcloudaccess can skip signed URLs entirely and rungcloud storage cp -r gs://your-bucket/prefix .(or the oldergsutil -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.
Download All Firebase Storage Files as a ZIP
List a Cloud Storage for Firebase folder, turn the files into a ZIP in the browser, and scale past tab limits with Eazip Cloud.
Download Azure Blob Storage Files as a ZIP
List a container prefix, generate SAS URLs on your server, and package the results into one ZIP with @eazip/core in the browser.