Eazip
Guides

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.

Azure Blob Storage has no built-in "download container as ZIP" action, so you list the blobs under a prefix, generate a short-lived SAS URL for each one, and hand the resulting { url, filename } list to @eazip/core. Storage account keys stay on the server, and the browser does the archiving.

Loading live demo…

Prepare SAS URLs on the server

Your backend endpoint should:

  1. authenticate the application user;
  2. list the blobs under the requested container and prefix;
  3. authorize the request against those specific blobs; and
  4. return short-lived SAS URLs, never the storage account key.
server/export.ts
import {
  BlobServiceClient,
  StorageSharedKeyCredential,
  BlobSASPermissions,
} from '@azure/storage-blob';

const credential = new StorageSharedKeyCredential(
  process.env.AZURE_STORAGE_ACCOUNT!,
  process.env.AZURE_STORAGE_KEY!,
);

const blobServiceClient = new BlobServiceClient(
  `https://${process.env.AZURE_STORAGE_ACCOUNT}.blob.core.windows.net`,
  credential,
);

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

export async function listContainerAsSignedUrls(
  containerName: string,
  prefix: string,
): Promise<SignedSource[]> {
  const containerClient = blobServiceClient.getContainerClient(containerName);
  const expiresOn = new Date(Date.now() + 15 * 60 * 1000);
  const sources: SignedSource[] = [];

  for await (const blob of containerClient.listBlobsFlat({ prefix })) {
    const blobClient = containerClient.getBlobClient(blob.name);
    const url = await blobClient.generateSasUrl({
      permissions: BlobSASPermissions.parse('r'),
      expiresOn,
    });

    sources.push({ url, filename: blob.name.slice(prefix.length) });
  }

  return sources;
}

listBlobsFlat({ prefix }) returns an async iterable, so there is no continuation-token loop to write yourself. Keep expiresOn short, but long enough for every file to finish downloading; a URL that expires mid-fetch fails only that file, and the archive is still usable if you leave failOnUrlError at its default.

generateSasUrl() needs a client built with an account key, as above. If your service uses Azure AD or a managed identity instead, call blobServiceClient.getUserDelegationKey() and pass the result to generateBlobSASQueryParameters() in place of the account key credential; the returned query string appends to the blob's URL the same way.

Configure Blob service CORS

The browser fetches each blob directly from Azure, so the storage account must allow your application's origin. Add a Blob service CORS rule with the Azure CLI:

az storage cors add \
  --account-name youraccount \
  --services b \
  --methods GET HEAD OPTIONS \
  --origins "https://app.example.com" \
  --allowed-headers "*" \
  --exposed-headers "*" \
  --max-age 3600

Or set the same rule in the portal under the storage account's Settings > Resource sharing (CORS), on the Blob service tab. Without this, the browser blocks the fetch and Eazip reports the file as failed even though the SAS URL itself is valid.

Create the ZIP in the browser

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

const response = await fetch('/api/exports/container-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: 'blob-export.zip',
});

result.download();

createZip() resolves once every reachable blob 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 blobs 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 blobs;
  • the storage account cannot allow browser CORS, for example a locked-down compliance account;
  • the export should survive a page reload; or
  • you would rather not hold archive bytes in tab memory at all.

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

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

result.downloadAll();

Add the storage account's hostname to the Public App's allowed source hosts, and make the SAS expiresOn long enough for Eazip Cloud to start and finish reading each blob. See When to use Eazip Cloud for the full comparison with Local.

Limits and alternatives

Every approach here still pays Azure's standard outbound data transfer pricing: blobs served to a fetch in the browser, or to Eazip Cloud, count as egress from the storage account, typically billed after the first 100 GB/month across your Azure subscription (rates vary by region and tier). Cloud's stream mode removes the archive from your visitor's browser, but it does not remove that underlying egress cost — someone still fetches every byte from Azure once.

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

  • A single blob doesn't need a ZIP at all; return one SAS URL and let the browser download it directly.
  • A bulk copy between storage accounts or to a local disk is better served by azcopy, Microsoft's own CLI, which is built for that job and does not go through a browser tab at all.
  • A server-side archive, built with an Azure Function that streams blobs into a ZIP and writes the result back to storage, avoids sending bytes through the visitor's browser, but you take on the Function's execution time limit and temporary storage limit yourself. Eazip Cloud exists largely so you don't have to build and operate that path.

FAQ

How do I download an entire Azure Blob Storage container as a ZIP?

List the blobs under the container (or a prefix) from your server, generate a SAS URL for each one, and pass the { url, filename } list to createZip() from @eazip/core. Azure has no native "download container as ZIP" operation, so the listing and archiving both happen outside the storage service itself.

Can I download multiple files from Azure Blob Storage as one ZIP?

Yes. createZip() accepts an array of SAS URLs and produces one ZIP in the browser, or in Eazip Cloud for larger sets. See Create a ZIP from remote URLs for the general shape, and the example above for the Azure-specific listing and signing step.

Why does my Azure SAS URL ZIP download fail with a CORS error?

The browser is fetching each SAS URL directly from Azure, and the storage account has no CORS rule allowing your origin. Add the CORS rule shown above, and confirm AllowedMethods includes GET for the app's exact origin.

Do I need an Azure AD identity to generate the SAS URLs?

No. An account-key credential is enough, as shown above. Use a user delegation key with getUserDelegationKey() instead if your service already authenticates with Azure AD or a managed identity and you would rather avoid distributing the account key.

How large a container 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 blobs, or that needs to survive a reload, use strategy: 'cloud' as shown above.