Download Backblaze B2 Files as a ZIP
List a Backblaze B2 folder, presign each object with the S3-compatible API, and package the results into one ZIP in the browser.
Backblaze B2 has no built-in "download folder as ZIP" button. List the objects
under a prefix, presign each one with B2's S3-compatible API, and hand the
resulting { url, filename } list to @eazip/core. Storage credentials stay
on the server, and the browser (or Eazip Cloud) does the archiving.
Loading live demo…
Prepare presigned 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 your B2 application key.
B2's S3-compatible API works with the standard AWS SDK. Point it at your
bucket's region-specific endpoint, for example
s3.us-west-004.backblazeb2.com:
import {
S3Client,
ListObjectsV2Command,
GetObjectCommand,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const REGION = 'us-west-004';
const b2 = new S3Client({
endpoint: `https://s3.${REGION}.backblazeb2.com`,
region: REGION,
credentials: {
accessKeyId: process.env.B2_KEY_ID!,
secretAccessKey: process.env.B2_APPLICATION_KEY!,
},
});
type SignedSource = {
url: string;
filename: string;
};
export async function listPrefixAsSignedUrls(
bucket: string,
prefix: string,
): Promise<SignedSource[]> {
const sources: SignedSource[] = [];
let continuationToken: string | undefined;
do {
const page = await b2.send(
new ListObjectsV2Command({
Bucket: bucket,
Prefix: prefix,
ContinuationToken: continuationToken,
}),
);
for (const object of page.Contents ?? []) {
if (!object.Key || object.Key.endsWith('/')) continue;
const url = await getSignedUrl(
b2,
new GetObjectCommand({ Bucket: bucket, Key: object.Key }),
{ expiresIn: 900 },
);
sources.push({ url, filename: object.Key.slice(prefix.length) });
}
continuationToken = page.NextContinuationToken;
} while (continuationToken);
return sources;
}Use an application key scoped to the bucket, not your master application key,
which the S3-compatible API rejects. ListObjectsV2Command returns at most
1,000 keys per page, so paginate with ContinuationToken for larger folders.
Keep expiresIn short, but long enough for every file to finish downloading;
a link that expires mid-fetch fails only that one file, and the archive is
still usable if you leave failOnUrlError at its default.
A native B2 alternative exists
You can also sign downloads with B2's native API:
b2_get_download_authorization returns a token you append to the file's
download URL as ?Authorization=.... The S3-compatible flow above is
usually simpler to integrate because it reuses the standard AWS SDK.
Configure B2 CORS rules
The browser fetches each object directly from B2, so the bucket needs a CORS rule allowing your application's origin. Using the B2 CLI:
b2 bucket update --cors-rules '[
{
"corsRuleName": "downloadFromApp",
"allowedOrigins": ["https://app.example.com"],
"allowedOperations": ["b2_download_file_by_name", "b2_download_file_by_id"],
"allowedHeaders": ["range"],
"exposeHeaders": ["x-bz-content-sha1"],
"maxAgeSeconds": 3600
}
]' your-bucket-nameYou can set the same rule from the B2 web UI's bucket CORS editor, or through
the S3-compatible API's PutBucketCors call using the standard AWS
AllowedOrigins / AllowedMethods shape. Without a matching rule, the
browser blocks the fetch and Eazip reports the file as failed even though the
signed URL itself is valid.
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();
const result = await createZip({
files,
zipName: 'b2-export.zip',
});
result.download();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. 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 your app's origin;
- 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: 'b2-export.zip',
});
result.downloadAll();Add the bucket's hostname to the Public App's allowed source hosts, and make the presigned URL lifetime long enough for Eazip Cloud to start and finish reading each object. Cloud runs in stream mode by default, which does not retain the archive after delivery; switch to stored mode if you need a reusable download link instead.
Limits and alternatives
B2 is priced to make this pattern cheap: free egress up to roughly 3x your average monthly stored data, and free egress through a Bandwidth Alliance partner such as Cloudflare beyond that, so a browser-side ZIP rarely adds transfer cost on its own. Eazip Cloud's stream mode still reads every object once from B2, so it inherits whatever egress terms apply to your account.
Eazip is not the right tool for every B2 download:
- A single object doesn't need a ZIP at all; return one presigned URL and let the browser download it directly.
- A public, static prefix can sometimes be served through a CDN with its own bulk-download feature; check before building a custom export flow.
- A server-side archive, built by streaming objects into a ZIP on a worker process and uploading the result back to B2, avoids sending bytes through the visitor's browser, but you take on writing and operating that pipeline yourself. Eazip Cloud exists largely so you don't have to.
FAQ
How do I download a Backblaze B2 folder as a ZIP?
List the objects under the folder's prefix with ListObjectsV2Command,
presign each one with getSignedUrl against B2's S3-compatible endpoint, and
pass the resulting { url, filename } list to createZip(). There is no B2
operation that returns a prefix as a single archive, so some client has to
fetch and package the objects; Eazip does that in the browser or in Cloud.
Does Backblaze B2 support presigned URLs?
Yes, through its S3-compatible API using standard AWS SigV4 query-string
signing — the same getSignedUrl call shown above works against B2's
endpoint. B2's native API offers an equivalent with
b2_get_download_authorization, which returns a token appended to the
download URL instead of a fully self-contained signed link.
Why does my B2 ZIP download fail with a CORS error?
The browser is fetching each signed URL directly from B2, and the bucket has
no CORS rule allowing your origin. Add a rule like the one above and confirm
it covers b2_download_file_by_name (or GET if set through the
S3-compatible API) for your app's exact origin.
Is bulk-downloading from B2 free?
Mostly. B2 includes a free egress allowance tied to stored data, plus free egress through a Bandwidth Alliance CDN partner. Confirm current terms on Backblaze's pricing page before relying on either for a high-volume export.
How large a B2 folder 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.
Download DigitalOcean Spaces Files as a ZIP
Presign DigitalOcean Spaces objects on the server, then package them into one browser-downloaded ZIP with Eazip.
Download Wasabi Files as a ZIP
Presign Wasabi Hot Cloud Storage objects on the server, then package them into one ZIP in the browser with Eazip — no storage credentials client-side.