---
title: "Upload & Manage Files with Firebase Storage in Capacitor"
description: Store user files in a Capacitor app with Firebase Cloud Storage, covering bucket setup, uploads and downloads, security rules, and troubleshooting.
date:
  created: 2026-07-31
  updated: 2026-07-31
authors:
  - djabif
categories:
  - Capacitor
  - Firebase
  - Guides
  - SDKs
links:
  - Capacitor Firebase Cloud Storage: sdks/capacitor/firebase/cloud-storage.md
faq: true
---

# Upload & Manage Files with Firebase Storage in Capacitor

Profile pictures, user-uploaded documents, generated PDFs — every app eventually needs somewhere to put files that isn't the device itself. The [Capacitor Firebase Cloud Storage plugin](../../sdks/capacitor/firebase/cloud-storage.md) gives you native upload/download SDKs for Android and iOS, plus the Firebase JS SDK on web, behind one shared API.

This guide walks the whole thing end to end — creating the storage bucket, installing and configuring the plugin, uploading and downloading files — plus the platform differences that catch teams off guard and the production practices worth knowing before you ship.

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="/" target="_blank">
    <img alt="Build and deploy your Capacitor app with Capawesome Cloud" src="https://capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
  </a>
</div>

## How to Use Firebase Cloud Storage in a Capacitor App

Adding Firebase Cloud Storage to a Capacitor app takes four steps:

1. **Create a Storage bucket** in your Firebase project and set its security rules.
2. **Install** `@capacitor-firebase/storage` and sync the native projects.
3. **Add the native config files** (`google-services.json` on Android, `GoogleService-Info.plist` on iOS).
4. **Upload and download files** with `uploadFile()` and `downloadFile()`, and get a shareable link with `getDownloadUrl()`.

The rest of this guide covers each step in detail, plus the platform gaps, best practices, and troubleshooting.

## What Is Cloud Storage?

[Cloud Storage for Firebase](https://firebase.google.com/docs/storage){:target="_blank"} is Google's object storage service for user-generated content: images, videos, documents, anything you'd otherwise store as a file. The [Capacitor Firebase Cloud Storage plugin](../../sdks/capacitor/firebase/cloud-storage.md) exposes the native Android and iOS Storage SDKs, plus the Firebase JS SDK on web, behind one shared TypeScript API.

A working example can be found here: [capawesome-team/capacitor-firebase-plugin-demo](https://github.com/capawesome-team/capacitor-firebase-plugin-demo){:target="_blank"}.

### Why Not Just Use the Firebase JS SDK?

Uploading and downloading through the JS SDK inside a WebView means routing file data through the WebView's JavaScript bridge, which gets expensive for anything beyond small files, large uploads and downloads risk timeouts or memory pressure that the native SDKs handle more gracefully. The native SDKs also integrate directly with each platform's file system: on Android and iOS you pass a file `uri` straight to [`uploadFile(...)`](../../sdks/capacitor/firebase/cloud-storage.md#uploadfile) or [`downloadFile(...)`](../../sdks/capacitor/firebase/cloud-storage.md#downloadfile), no intermediate `Blob` conversion required, which only exists as a concept on web.

!!! tip "Advanced Operations"

    This guide covers the operations most apps need. For every method with code samples, see the [Usage section of the plugin docs](https://capawesome.io/docs/sdks/capacitor/firebase/cloud-storage/#usage){:target="_blank"}.

## Why Use Firebase Storage in a Capacitor App?

The plugin's own use cases map to four real scenarios:

**User-generated content.** Upload photos or other files created by your users with [`uploadFile(...)`](../../sdks/capacitor/firebase/cloud-storage.md#uploadfile). Pass a `uri` on Android/iOS or a `blob` on web:

```typescript
import { FirebaseStorage } from '@capacitor-firebase/storage';

const uploadFile = async () => {
  return new Promise((resolve, reject) => {
    FirebaseStorage.uploadFile(
      { path: 'images/mountains.png', uri: 'file:///.../mountains.png' },
      (event, error) => {
        if (error) {
          reject(error);
        } else if (event?.completed) {
          resolve();
        }
      },
    );
  });
};
```

**File downloads.** Download files to the local file system on Android/iOS, or as a `Blob` on web, with [`downloadFile(...)`](../../sdks/capacitor/firebase/cloud-storage.md#downloadfile).

**Content delivery.** Retrieve a file's download URL with [`getDownloadUrl(...)`](../../sdks/capacitor/firebase/cloud-storage.md#getdownloadurl) to display or share it:

```typescript
import { FirebaseStorage } from '@capacitor-firebase/storage';

const getDownloadUrl = async () => {
  const { downloadUrl } = await FirebaseStorage.getDownloadUrl({
    path: 'images/mountains.png',
  });
  return downloadUrl;
};
```

**File management.** List files in a directory with [`listFiles(...)`](../../sdks/capacitor/firebase/cloud-storage.md#listfiles), read/update metadata, or delete files:

```typescript
import { FirebaseStorage } from '@capacitor-firebase/storage';

const listFiles = async () => {
  const { items } = await FirebaseStorage.listFiles({ path: 'images' });
  return items;
};
```

## Before You Start

This guide assumes you already have a Capacitor app with the `android` and/or `ios` platforms added, and a Firebase project (create one in the [Firebase console](https://console.firebase.google.com/){:target="_blank"} if you haven't). Everything specific to Cloud Storage we'll set up below.

## Step 1: Create the Storage Bucket

1. In the [Firebase console](https://console.firebase.google.com/){:target="_blank"}, open **Build → Storage** and click **Get started**.
2. Choose a starting mode for the security rules — **production mode** (locked to authenticated users) is the safe default; test mode allows open access for a short window and is only for prototyping — then pick your bucket location.
3. Under the **Rules** tab, set rules that match how your app authenticates. Firebase's default already requires a signed-in user:

    ```
    rules_version = '2';
    service firebase.storage {
      match /b/{bucket}/o {
        match /{allPaths=**} {
          allow read, write: if request.auth != null;
        }
      }
    }
    ```

    Because those rules check `request.auth`, uploads and downloads fail until the user is signed in — see the [Capacitor Firebase Authentication guide](./capacitor-firebase-authentication-guide.md).

## Step 2: Install the Plugin

```bash
npm install @capacitor-firebase/storage
npx cap sync
```

## Step 3: Add Firebase to Your Native Apps

The plugin uses the native config files Firebase generates ([full reference](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md){:target="_blank"}):

- **Android** — register an Android app, download `google-services.json`, and place it in `android/app/google-services.json`.
- **iOS** — register an iOS app, download `GoogleService-Info.plist`, move it to `ios/App/App/GoogleService-Info.plist`, and drag it into the Xcode project (add it to all targets).

On **iOS with Swift Package Manager**, add this to `capacitor.config.ts` to avoid a package identity collision (requires Capacitor CLI 8.4.0+):

```json
{
  "experimental": {
    "ios": {
      "spm": {
        "packageOptions": {
          "@capacitor-firebase/storage": { "symlink": true }
        }
      }
    }
  }
}
```

The plugin itself needs no additional configuration.

## Tracking Upload Progress in Angular, React, or Vue

`uploadFile(...)` reports progress through its callback, which you'll usually surface as a progress bar. The wiring differs by framework — and in Angular the callback fires outside the change-detection zone, so update state inside `NgZone.run(...)`:

=== "Angular"

    ```typescript
    import { Injectable, NgZone, signal } from '@angular/core';
    import { FirebaseStorage } from '@capacitor-firebase/storage';

    @Injectable({ providedIn: 'root' })
    export class UploadService {
      readonly progress = signal(0);

      constructor(private readonly zone: NgZone) {}

      upload(path: string, uri: string) {
        FirebaseStorage.uploadFile({ path, uri }, (event, error) => {
          if (!error && event) {
            this.zone.run(() => this.progress.set(event.progress ?? 0));
          }
        });
      }
    }
    ```

=== "React"

    ```tsx
    import { useState } from 'react';
    import { FirebaseStorage } from '@capacitor-firebase/storage';

    export const useUpload = () => {
      const [progress, setProgress] = useState(0);
      const upload = (path: string, uri: string) => {
        FirebaseStorage.uploadFile({ path, uri }, (event, error) => {
          if (!error && event) setProgress(event.progress ?? 0);
        });
      };
      return { progress, upload };
    };
    ```

=== "Vue"

    ```typescript
    import { ref } from 'vue';
    import { FirebaseStorage } from '@capacitor-firebase/storage';

    export const useUpload = () => {
      const progress = ref(0);
      const upload = (path: string, uri: string) => {
        FirebaseStorage.uploadFile({ path, uri }, (event, error) => {
          if (!error && event) progress.value = event.progress ?? 0;
        });
      };
      return { progress, upload };
    };
    ```

## Run on a Device and Verify

Build and launch on a device or emulator so the native Storage SDKs are used:

```bash
npx cap sync
npx cap run android   # or: npx cap run ios
```

Upload a file from your UI, then confirm it landed:

- **In the Firebase console**, open **Storage → Files** — your uploaded file should appear at the path you used.
- **In your app**, call `getDownloadUrl(...)` for that path and confirm you get back a working URL.

A permission error here almost always means your security rules (Step 1) rejected an unauthenticated request, not a bug in the upload call.

## Firebase Cloud Storage Best Practices

### Prefer the Filesystem Plugin for Files You Need to Keep Around

`downloadFile(...)` works, but for anything you need to reference again later, resuming a paused download, showing a locally cached copy, it's often cleaner to fetch just the [`getDownloadUrl(...)`](../../sdks/capacitor/firebase/cloud-storage.md#getdownloadurl) and hand it to the official [Capacitor Filesystem](https://capacitorjs.com/docs/apis/filesystem){:target="_blank"} plugin, which gives you full control over the destination directory and file naming:

```typescript
import { FirebaseStorage } from '@capacitor-firebase/storage';
import { Filesystem, Directory } from '@capacitor/filesystem';

const downloadWithFilesystem = async () => {
  const { downloadUrl } = await FirebaseStorage.getDownloadUrl({
    path: 'images/mountains.png',
  });
  const { path } = await Filesystem.downloadFile({
    url: downloadUrl,
    path: 'mountains.png',
    directory: Directory.Cache,
  });
  return path;
};
```

For a deeper look at managing files locally without running into memory issues, see [Capacitor File Handling: The Complete Guide](./capacitor-file-handling-guide.md). If the goal is letting the user open the downloaded file (a PDF receipt, a downloaded document) rather than just caching it, the [Capacitor File Opener plugin](../../sdks/capacitor/file-opener.md) hands it off to the device's default app for that file type in one call.

### Track Progress Correctly, Including the Platform Gap

Both `uploadFile(...)` and `downloadFile(...)` report `progress` as a fraction between 0 and 1 on every platform, but `bytesTransferred` and `totalBytes` are **only available on Android and Web**, not iOS. If you're building a progress bar that shows "12 MB of 40 MB", it needs a fallback (percentage only) for iOS rather than assuming those two fields will always be there.

### Paginate Large Directories Explicitly

[`listFiles(...)`](../../sdks/capacitor/firebase/cloud-storage.md#listfiles) defaults to a `maxResults` of 1000. If a directory can realistically grow past that (a shared uploads folder, a growing media library), don't assume you got everything back on the first call, check whether a `pageToken` was returned and keep paginating instead of silently truncating your file list.

### Understand the Default Security Rules Before You Get "Permission Denied"

Cloud Storage ships with one of two rule modes when you set it up: **locked mode**, Firebase's default, which only allows access to **authenticated users**, or test mode, which allows anyone. If uploads or downloads fail with a permission error the moment you test on a real project, check your Storage Security Rules before assuming the plugin is broken; it's very often that the current user isn't authenticated at all. See the [Capacitor Firebase Authentication guide](./capacitor-firebase-authentication-guide.md) if you haven't wired up sign-in yet.

### Set `contentType` When Uploading

Without an explicit `contentType` in the upload metadata, some clients (particularly browsers) may not render or handle the file the way you expect, an image might download instead of displaying inline, for example. Set it explicitly for anything you intend to display directly rather than relying on Firebase's default inference:

```typescript
import { FirebaseStorage } from '@capacitor-firebase/storage';

const uploadWithMetadata = async () => {
  return new Promise((resolve, reject) => {
    FirebaseStorage.uploadFile(
      {
        path: 'images/mountains.png',
        uri: 'file:///.../mountains.png',
        metadata: { contentType: 'image/png' },
      },
      (event, error) => {
        if (error) {
          reject(error);
        } else if (event?.completed) {
          resolve();
        }
      },
    );
  });
};
```

### Let Users Pick the File, Don't Build Your Own File Browser

For letting users choose what to upload, pair Storage with the [Capacitor File Picker plugin](../../sdks/capacitor/file-picker.md) instead of building custom file-selection UI. Its result already gives you exactly what `uploadFile(...)` needs: a `path` on Android/iOS and a `blob` on Web.

```typescript
import { FilePicker } from '@capawesome/capacitor-file-picker';
import { FirebaseStorage } from '@capacitor-firebase/storage';

const pickAndUpload = async () => {
  const { files } = await FilePicker.pickFiles();
  const file = files[0];
  return new Promise((resolve, reject) => {
    FirebaseStorage.uploadFile(
      { path: `uploads/${file.name}`, uri: file.path, blob: file.blob },
      (event, error) => {
        if (error) {
          reject(error);
        } else if (event?.completed) {
          resolve();
        }
      },
    );
  });
};
```

### Compress Images Before Uploading

If you're uploading a lot of user-submitted photos, compressing them first saves both storage costs and upload time, especially on mobile networks. The [Capacitor File Compressor plugin](../../sdks/capacitor/file-compressor.md), a [Capawesome Insiders](https://capawesome.io/insiders/){:target="_blank"} plugin, exists specifically for this and accepts the same `path`/`blob` shape Storage uses:

```typescript
import { FileCompressor } from '@capawesome-team/capacitor-file-compressor';

const compressBeforeUpload = async (path: string) => {
  const { path: compressedPath } = await FileCompressor.compressImage({
    path,
    mimeType: 'image/jpeg',
    quality: 0.7,
  });
  return compressedPath;
};
```

This is exactly the pattern used in the [Trip Expenses demo app](./how-to-wrap-an-angular-app-with-capacitor-and-firebase.md): pick a receipt image, compress it, then upload it to Cloud Storage.

## Limitations

- The `blob` option on `uploadFile(...)` is only available on Web; `uri` is only available on Android and iOS.
- The `uri` option on `downloadFile(...)` (where the file is saved) is only available on Android and iOS; on web the file comes back as a `Blob` in the callback instead.
- `bytesTransferred` and `totalBytes` on upload/download progress events are only available on Android and Web, not iOS.

## Common Errors and Troubleshooting

- **`storage/unauthorized` or permission denied on upload/download.** Your Storage security rules are rejecting the request — usually an unauthenticated user hitting rules that require `request.auth != null`. Confirm sign-in and check the rules (Step 1).
- **App crashes on launch, or Storage isn't configured.** The `google-services.json` / `GoogleService-Info.plist` file is missing, misplaced, or (on iOS) wasn't added to the Xcode project. Re-check Step 3 and run `npx cap sync`.
- **`uri` is undefined on web, or `blob` is undefined on native.** `uri` is Android/iOS only and `blob` is web only — pass the right one per platform. A [File Picker](../../sdks/capacitor/file-picker.md) result already gives you both.
- **A progress bar shows `NaN of NaN` on iOS.** `bytesTransferred` and `totalBytes` don't exist on iOS — fall back to the `progress` fraction (0–1) there.
- **`listFiles(...)` seems to miss files.** It returns at most `maxResults` (1000 by default) per call. Keep paginating with the returned `pageToken` until it's absent.

## FAQ

### How do I upload an image to Firebase Cloud Storage in a Capacitor app?

Pick the file with the [Capacitor File Picker plugin](../../sdks/capacitor/file-picker.md), then pass its result to [`uploadFile(...)`](../../sdks/capacitor/firebase/cloud-storage.md#uploadfile) — a `uri` on Android and iOS, or a `blob` on the web. The callback reports progress and sets `completed` to `true` when the upload finishes. Make sure the user is signed in first if your security rules require it.

### Is Cloud Storage free to use?

There's a free tier (a modest amount of stored data plus a daily download quota), then usage-based pricing for storage and bandwidth beyond that, unlike Firebase Analytics, which has no usage-based cost at all. Exact quotas differ depending on your bucket type and region, so check the current [Firebase pricing page](https://firebase.google.com/pricing){:target="_blank"} rather than assuming a fixed number.

### Why am I getting a permission-denied error on upload or download?

Almost always the Storage Security Rules, not the plugin. Firebase's default "locked mode" rules only allow access to authenticated users, so an anonymous or signed-out user will get denied by design. Confirm the user is actually signed in, and check your project's Storage Rules in the Firebase console before assuming there's a bug in the upload/download call itself.

### What happens if a directory has more than 1000 files?

`listFiles(...)` only returns up to `maxResults` (1000 by default) per call. If the directory has more, the result includes a `pageToken` you pass into the next call to continue listing, rather than the method returning everything at once.

## Ship Upload Fixes Without a Full Release

The code that picks, compresses, and uploads files runs in your app's web layer, so a fix to that flow doesn't have to wait on an app store review. [Capawesome Cloud](https://capawesome.io/){:target="_blank"} builds your iOS and Android apps in the cloud and pushes web-layer changes straight to users with live updates — handy when a file-handling edge case only shows up in production — and automates App Store and Play Store submission when you do ship a native release.

[Book a Capawesome Cloud Demo](https://cal.com/team/capawesome/cloud-demo){ .md-button .md-button--primary }

## Conclusion

Cloud Storage's role in a Capacitor app is straightforward: get files in and out reliably across Android, iOS, and web. The parts worth planning for up front are the platform gaps: `blob` vs. `uri`, progress fields that don't exist on iOS, pagination past 1000 files, and Security Rules that deny anonymous access by default.

If you want to go deeper from here:

- [Firebase Authentication in Capacitor: Setup & Best Practices](./capacitor-firebase-authentication-guide.md) — needed if your Storage rules require a signed-in user.
- [Capacitor Firestore: Real-Time Data & Offline Sync](./capacitor-firebase-cloud-firestore-guide.md) — often paired with Storage to save file references alongside structured data.
- [Track App Events with Firebase Analytics in Capacitor](./capacitor-firebase-analytics-guide.md) — the sibling Firebase plugin for measuring how users engage with your app.
- [Capacitor File Handling: The Complete Guide](./capacitor-file-handling-guide.md) — for managing downloaded files locally without running into memory issues.

Questions or something you ran into that isn't covered here? Drop into the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"} — and subscribe to the [Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"} if you want the next deep-dive in your inbox.
