# How to Upload Files to Firebase Storage

- Tool: Firebase
- Difficulty: Intermediate
- Time required: 15-20 min
- Compatibility: Firebase JS SDK v9+, Firebase Storage on Blaze or Spark plan
- Last updated: March 2026

## TL;DR

To upload files to Firebase Storage, use uploadBytes() for simple uploads or uploadBytesResumable() for large files that need progress tracking and pause/resume capability. Configure Storage security rules to control who can upload and what file types and sizes are allowed. After uploading, retrieve the download URL with getDownloadURL() to display or share the file. Always validate file type and size on the client before uploading.

## Uploading Files to Firebase Storage

Firebase Cloud Storage provides scalable file storage backed by Google Cloud Storage. This tutorial walks through uploading files from a web app using the modular SDK, tracking upload progress for large files, writing security rules that restrict uploads to authenticated users with file type and size validation, and retrieving download URLs to display uploaded content. You will build a complete upload flow with error handling and progress feedback.

## Before you start

- A Firebase project with Storage enabled (default bucket created)
- Firebase JS SDK v9+ installed in your project
- Firebase Auth configured for user authentication
- Basic understanding of JavaScript File and Blob objects

## Step-by-step guide

### 1. Initialize Firebase Storage and create a reference

Import getStorage and ref from firebase/storage. A storage reference points to a location in your bucket where the file will be uploaded. Organize files using path segments like user IDs to keep uploads structured and to simplify security rules. The reference does not create any storage until you upload a file to it.

```
import { initializeApp } from 'firebase/app';
import { getStorage, ref } from 'firebase/storage';

const app = initializeApp(firebaseConfig);
const storage = getStorage(app);

// Create a reference to a file path
const fileRef = ref(storage, `uploads/${userId}/${fileName}`);
```

**Expected result:** A storage reference object pointing to the specified path, ready for upload operations.

### 2. Upload a file with uploadBytes()

For simple uploads where you do not need progress tracking, use uploadBytes(). It accepts a storage reference and a File or Blob object, and returns a Promise that resolves with an UploadResult containing the file metadata. This is the simplest upload method and works well for files under a few megabytes.

```
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';

async function uploadFile(file: File, userId: string) {
  const fileRef = ref(storage, `uploads/${userId}/${Date.now()}-${file.name}`);

  // Upload the file
  const snapshot = await uploadBytes(fileRef, file, {
    contentType: file.type,
    customMetadata: { uploadedBy: userId },
  });

  // Get the download URL
  const url = await getDownloadURL(snapshot.ref);
  return { path: snapshot.ref.fullPath, url };
}
```

**Expected result:** The file is uploaded to Firebase Storage and you receive the full path and a download URL.

### 3. Upload with progress tracking using uploadBytesResumable()

For larger files, use uploadBytesResumable() which returns an UploadTask with progress events, pause/resume capability, and better error recovery. Listen to the 'state_changed' event to track upload progress as a percentage. The task also fires on error and on completion.

```
import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';

function uploadWithProgress(
  file: File,
  userId: string,
  onProgress: (percent: number) => void
): Promise<string> {
  return new Promise((resolve, reject) => {
    const fileRef = ref(storage, `uploads/${userId}/${Date.now()}-${file.name}`);
    const uploadTask = uploadBytesResumable(fileRef, file, {
      contentType: file.type,
    });

    uploadTask.on(
      'state_changed',
      (snapshot) => {
        const percent = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        onProgress(Math.round(percent));
      },
      (error) => reject(error),
      async () => {
        const url = await getDownloadURL(uploadTask.snapshot.ref);
        resolve(url);
      }
    );
  });
}

// Usage with pause/resume
// uploadTask.pause();
// uploadTask.resume();
// uploadTask.cancel();
```

**Expected result:** The file uploads with real-time progress updates, and the download URL is returned on completion.

### 4. Write Storage security rules for authenticated uploads

Storage security rules control who can read, write, and delete files. The most common pattern restricts uploads to authenticated users writing to their own user-ID folder. You can also validate file size and content type in the rules to prevent abuse.

```
// storage.rules
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /uploads/{userId}/{allPaths=**} {
      // Only the file owner can upload and read
      allow read: if request.auth != null
        && request.auth.uid == userId;

      allow write: if request.auth != null
        && request.auth.uid == userId
        // Limit file size to 10 MB
        && request.resource.size < 10 * 1024 * 1024
        // Allow only images and PDFs
        && request.resource.contentType.matches('image/.*|application/pdf');

      allow delete: if request.auth != null
        && request.auth.uid == userId;
    }
  }
}
```

**Expected result:** Only authenticated users can upload to their own folder, files are limited to 10 MB, and only images and PDFs are accepted.

### 5. Validate files on the client before uploading

Client-side validation provides instant feedback and saves bandwidth by rejecting invalid files before they are sent to Firebase. Check the file's MIME type and size before calling any upload function. This complements server-side security rules, which are the true enforcement layer.

```
const ALLOWED_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'application/pdf'];
const MAX_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB

function validateFile(file: File): { valid: boolean; error?: string } {
  if (!ALLOWED_TYPES.includes(file.type)) {
    return { valid: false, error: `File type '${file.type}' is not allowed.` };
  }
  if (file.size > MAX_SIZE_BYTES) {
    const sizeMB = (file.size / (1024 * 1024)).toFixed(1);
    return { valid: false, error: `File is ${sizeMB} MB. Maximum is 10 MB.` };
  }
  return { valid: true };
}
```

**Expected result:** Invalid files are rejected immediately with a clear error message before any upload attempt.

### 6. Configure CORS for cross-origin access

If your app runs on a different domain than your Firebase Storage bucket (common during local development), you need to configure CORS on the bucket. Without CORS configuration, getDownloadURL() works but direct fetch() calls to Storage URLs will be blocked by the browser. Use gsutil to apply a CORS configuration JSON file.

```
// cors.json — save this file locally
[
  {
    "origin": ["http://localhost:3000", "https://your-domain.com"],
    "method": ["GET", "POST", "PUT", "DELETE"],
    "maxAgeSeconds": 3600,
    "responseHeader": ["Content-Type", "Authorization"]
  }
]

// Apply with gsutil (run in your terminal)
// gsutil cors set cors.json gs://YOUR_PROJECT.appspot.com
```

**Expected result:** Browsers can make cross-origin requests to your Storage bucket without CORS errors.

## Complete code example

File: `firebase-upload.ts`

```typescript
import {
  getStorage,
  ref,
  uploadBytesResumable,
  getDownloadURL,
  UploadTask,
} from 'firebase/storage';
import { getAuth } from 'firebase/auth';
import { app } from './firebase';

const storage = getStorage(app);
const auth = getAuth(app);

const ALLOWED_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'application/pdf'];
const MAX_SIZE = 10 * 1024 * 1024;

interface UploadResult {
  success: boolean;
  url?: string;
  path?: string;
  error?: string;
}

function validateFile(file: File): string | null {
  if (!ALLOWED_TYPES.includes(file.type)) return `Type '${file.type}' not allowed.`;
  if (file.size > MAX_SIZE) return `File exceeds 10 MB limit.`;
  return null;
}

export function uploadFile(
  file: File,
  onProgress?: (percent: number) => void
): Promise<UploadResult> {
  return new Promise((resolve) => {
    const error = validateFile(file);
    if (error) return resolve({ success: false, error });

    const user = auth.currentUser;
    if (!user) return resolve({ success: false, error: 'Not authenticated.' });

    const filePath = `uploads/${user.uid}/${Date.now()}-${file.name}`;
    const fileRef = ref(storage, filePath);
    const task: UploadTask = uploadBytesResumable(fileRef, file, {
      contentType: file.type,
    });

    task.on(
      'state_changed',
      (snap) => {
        const pct = Math.round((snap.bytesTransferred / snap.totalBytes) * 100);
        onProgress?.(pct);
      },
      (err) => resolve({ success: false, error: err.message }),
      async () => {
        const url = await getDownloadURL(task.snapshot.ref);
        resolve({ success: true, url, path: filePath });
      }
    );
  });
}
```

## Common mistakes

- **Not configuring Storage security rules, leaving the default deny-all rule in place** — undefined Fix: Write explicit read and write rules for your upload paths. The default rules deny all access. Enable auth-based access for authenticated users.
- **Using uploadBytes() for large files without progress feedback, causing the UI to appear frozen** — undefined Fix: Use uploadBytesResumable() for files over 1 MB. It provides progress events, pause/resume, and better error recovery.
- **Forgetting to configure CORS on the Storage bucket, leading to browser errors when fetching files directly** — undefined Fix: Apply a CORS configuration with gsutil: gsutil cors set cors.json gs://your-bucket. Include your app's domain in the allowed origins.
- **Using the same file name for every upload, causing files to overwrite each other** — undefined Fix: Prepend a timestamp or UUID to each filename: `${Date.now()}-${file.name}` to ensure uniqueness.

## Best practices

- Use user-scoped paths (uploads/userId/filename) to organize files and simplify security rules
- Validate file type and size on the client before uploading for instant user feedback
- Always enforce file type and size limits in Storage security rules — client validation is for UX only
- Use uploadBytesResumable() for files over 1 MB to provide progress tracking and pause/resume support
- Set contentType in upload metadata to ensure files are served with the correct MIME type
- Prepend timestamps or UUIDs to file names to avoid naming collisions
- Configure CORS on your bucket during development and restrict origins to your production domain before launch
- Never expose download URLs publicly if the content is private — use security rules to restrict read access

## Frequently asked questions

### What is the maximum file size I can upload to Firebase Storage?

Firebase Storage supports files up to 5 TB. However, for web uploads you are limited by browser memory and connection stability. For practical purposes, use uploadBytesResumable for files over a few MB.

### Do I need the Blaze plan to use Firebase Storage?

The Spark (free) plan includes 5 GB of storage and 1 GB/day of downloads. The Spark plan blocks executable file uploads (.exe, .dll, .apk). Blaze removes this restriction and scales beyond free tier limits.

### Can I upload files without authenticating the user?

Yes, if your security rules allow unauthenticated writes. However, this is not recommended for production as anyone can upload files to your bucket and consume your storage quota.

### How do I delete an uploaded file?

Import deleteObject from firebase/storage and pass the file reference: await deleteObject(ref(storage, filePath)). Your security rules must allow delete for the authenticated user.

### Why does my upload fail with a CORS error?

Firebase Storage buckets have no CORS configuration by default. Apply a CORS config file with gsutil: gsutil cors set cors.json gs://your-bucket.appspot.com. Include your app's origin URL in the config.

### Can I resume a failed upload?

uploadBytesResumable() supports resume after a pause. However, if the upload fails due to a network error, you need to start a new upload. Firebase does not persist upload state across page reloads.

### Can RapidDev help build a file upload system with Firebase Storage?

Yes. RapidDev can help you implement secure file uploads with progress tracking, write Storage security rules, configure CORS, and build image processing pipelines with Firebase Extensions.

---

Source: https://www.rapidevelopers.com/firebase-tutorial/how-to-upload-files-to-firebase-storage
© RapidDev — https://www.rapidevelopers.com/firebase-tutorial/how-to-upload-files-to-firebase-storage
