46

I want to download an entire folder from Firebase storage. I can download single files using DownloadURL as follows, but it does not work for folders.

var storageRef = firebase.storage().ref();

// Create a reference to the file we want to download
var starsRef = storageRef.child(path);

// Get the download URL
starsRef.getDownloadURL().then(function(url) {
  // Insert url into an <img> tag to "download"
  ImageUrl = url;

  console.log(ImageUrl);
}).catch(function(error) {
  switch (error.code) {
    case 'storage/object_not_found':
      // File doesn't exist
      break;

    case 'storage/unauthorized':
      // User doesn't have permission to access the object
      break;

    case 'storage/canceled':
      // User canceled the upload
      break;

    case 'storage/unknown':
      // Unknown error occurred, inspect the server response
      break;
  }
});

How to download entire folder from Firebase?

pgSystemTester
  • 8,979
  • 2
  • 23
  • 49
isuru
  • 3,385
  • 4
  • 27
  • 62
  • Question on what you expect the intended behavior to be: an array of all files, a zipped folder containing all files, or something else? – Mike McDonald Jan 04 '17 at 15:44
  • @MikeMcDonald I want to download a zipped folder containing all files. – isuru Jan 04 '17 at 17:47
  • Here is an elegant solution that will solve all your problems, download all files at once as a single .zip file right on the client side: https://stackoverflow.com/a/66732338/8138591 (March 21, 2021) – Son Nguyen Mar 21 '21 at 12:47

5 Answers5

95

You can use gsutil to download the whole storage bucket

gsutil -m cp -R gs://<bucket_name> .
Lahiru Chandima
  • 22,324
  • 22
  • 103
  • 179
11

There is no API in Firebase Storage to download all files in a folder. You will have to download the files one by one, or create a zip file that contains all the files.

As Lahiru's answer shows it can be accomplished with gsutils, but that's a server-side operation - not something you'd run in your client-side application.

Related:

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • I cannot find the way to get all the files and create a zip file from above related links. Please give me some tips to find a way to create a zip file that contains all the files. Thanks for your response. – isuru Jan 04 '17 at 18:12
6

Command gustil for Windows !!!

gsutil cp -r gs://<bucket_name>.appspot.com/OBJECT_NAME "D:\path"

Use Cloud tools for PowerShell

REF for install windows >> https://cloud.google.com/storage/docs/gsutil_install

Flukedev8
  • 131
  • 2
  • 2
  • gsutil is also available for Mac, terminal command is same as above (change dest. path!)... https://cloud.google.com/storage/docs/gsutil_install#mac – RumbleFish Jun 05 '22 at 11:20
3

You can download the folder by creating a zip file of it.

Here is a sample function:

import JSZip from 'jszip';
import { saveAs } from 'file-saver';
import {
  getStorage,
  listAll,
  ref,
  getDownloadURL,
  getMetadata,
} from 'firebase/storage';
import { auth } from '../../Firebase';

export const downloadFolderAsZip = async () => {
  const jszip = new JSZip();
  const storage = getStorage();
  const folderRef = ref(
    storage,
    'images'
  );
  const folder = await listAll(folderRef);
  const promises = folder.items
    .map(async (item) => {
      const file = await getMetadata(item);
      const fileRef = ref(storage, item.fullPath);
      const fileBlob = await getDownloadURL(fileRef).then((url) => {
        return fetch(url).then((response) => response.blob());
      });
      jszip.file(file.name, fileBlob);
    })
    .reduce((acc, curr) => acc.then(() => curr), Promise.resolve());
  await promises;
  const blob = await jszip.generateAsync({ type: 'blob' });
  saveAs(blob, 'download.zip');
};
geethsg7
  • 205
  • 2
  • 11
0

For a recursive solution that includes subfolders in the zip file, see the following sample. You'll instantiate a jszip object, await promises from a function that zips files and traverses the directories, then save the zip. If the content is a file ("item"), it is zipped into the jszip object. If it is a folder ("prefix"), the function is called again with a new subpath, passing in the same jszip object. For further improvement, you may want to get contents with list and pagination if your contents are too many for listAll, since listAll limits retrievals.

import JSZip from 'jszip';
import { saveAs } from 'file-saver';
import {
  getStorage, ref, getBlob, listAll,
} from "firebase/storage";

const addFilesFromDirectoryToZip = async (directoryPath = "", zip) => {
  const storage = getStorage();
 
  const directoryContentsRef = ref(
    storage,
    directoryPath
  );
  const directoryContents = await listAll(directoryContentsRef);

  for (const file of directoryContents.items) {
    const fileRef = ref(storage, file.fullPath);
    const fileBlob = await getBlob(fileRef)
    zip.file(file.fullPath, fileBlob);
  }

  for (const folder of directoryContents.prefixes) {
    await addFilesFromDirectoryToZip(folder.fullPath, zip);
  };
};

export const downloadFolderAsZip = async (directoryPath = "") => {
  const zip = new JSZip();

  await addFilesFromDirectoryToZip(directoryPath, zip);

  const blob = await zip.generateAsync({ type: "blob" });
  const name = directoryPath.split('/').pop();
  saveAs(blob, name);
};
user3291025
  • 997
  • 13
  • 20