Skip to main content
RapidDev - Software Development Agency
firebase-tutorial

How to Upload Files to Firebase Storage

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.

What you'll learn

  • How to upload files with uploadBytes() and uploadBytesResumable() with progress tracking
  • How to write Storage security rules for auth-based upload access and file validation
  • How to retrieve download URLs with getDownloadURL() after upload
  • How to handle upload errors, pause and resume, and validate files client-side
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate8 min read15-20 minFirebase JS SDK v9+, Firebase Storage on Blaze or Spark planMarch 2026RapidDev Engineering Team
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.

Prerequisites

  • 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.

typescript
1import { initializeApp } from 'firebase/app';
2import { getStorage, ref } from 'firebase/storage';
3
4const app = initializeApp(firebaseConfig);
5const storage = getStorage(app);
6
7// Create a reference to a file path
8const 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.

typescript
1import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
2
3async function uploadFile(file: File, userId: string) {
4 const fileRef = ref(storage, `uploads/${userId}/${Date.now()}-${file.name}`);
5
6 // Upload the file
7 const snapshot = await uploadBytes(fileRef, file, {
8 contentType: file.type,
9 customMetadata: { uploadedBy: userId },
10 });
11
12 // Get the download URL
13 const url = await getDownloadURL(snapshot.ref);
14 return { path: snapshot.ref.fullPath, url };
15}

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.

typescript
1import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';
2
3function uploadWithProgress(
4 file: File,
5 userId: string,
6 onProgress: (percent: number) => void
7): Promise<string> {
8 return new Promise((resolve, reject) => {
9 const fileRef = ref(storage, `uploads/${userId}/${Date.now()}-${file.name}`);
10 const uploadTask = uploadBytesResumable(fileRef, file, {
11 contentType: file.type,
12 });
13
14 uploadTask.on(
15 'state_changed',
16 (snapshot) => {
17 const percent = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
18 onProgress(Math.round(percent));
19 },
20 (error) => reject(error),
21 async () => {
22 const url = await getDownloadURL(uploadTask.snapshot.ref);
23 resolve(url);
24 }
25 );
26 });
27}
28
29// Usage with pause/resume
30// uploadTask.pause();
31// uploadTask.resume();
32// 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.

typescript
1// storage.rules
2rules_version = '2';
3service firebase.storage {
4 match /b/{bucket}/o {
5 match /uploads/{userId}/{allPaths=**} {
6 // Only the file owner can upload and read
7 allow read: if request.auth != null
8 && request.auth.uid == userId;
9
10 allow write: if request.auth != null
11 && request.auth.uid == userId
12 // Limit file size to 10 MB
13 && request.resource.size < 10 * 1024 * 1024
14 // Allow only images and PDFs
15 && request.resource.contentType.matches('image/.*|application/pdf');
16
17 allow delete: if request.auth != null
18 && request.auth.uid == userId;
19 }
20 }
21}

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.

typescript
1const ALLOWED_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'application/pdf'];
2const MAX_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB
3
4function validateFile(file: File): { valid: boolean; error?: string } {
5 if (!ALLOWED_TYPES.includes(file.type)) {
6 return { valid: false, error: `File type '${file.type}' is not allowed.` };
7 }
8 if (file.size > MAX_SIZE_BYTES) {
9 const sizeMB = (file.size / (1024 * 1024)).toFixed(1);
10 return { valid: false, error: `File is ${sizeMB} MB. Maximum is 10 MB.` };
11 }
12 return { valid: true };
13}

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.

typescript
1// cors.json — save this file locally
2[
3 {
4 "origin": ["http://localhost:3000", "https://your-domain.com"],
5 "method": ["GET", "POST", "PUT", "DELETE"],
6 "maxAgeSeconds": 3600,
7 "responseHeader": ["Content-Type", "Authorization"]
8 }
9]
10
11// Apply with gsutil (run in your terminal)
12// 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 working example

firebase-upload.ts
1import {
2 getStorage,
3 ref,
4 uploadBytesResumable,
5 getDownloadURL,
6 UploadTask,
7} from 'firebase/storage';
8import { getAuth } from 'firebase/auth';
9import { app } from './firebase';
10
11const storage = getStorage(app);
12const auth = getAuth(app);
13
14const ALLOWED_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'application/pdf'];
15const MAX_SIZE = 10 * 1024 * 1024;
16
17interface UploadResult {
18 success: boolean;
19 url?: string;
20 path?: string;
21 error?: string;
22}
23
24function validateFile(file: File): string | null {
25 if (!ALLOWED_TYPES.includes(file.type)) return `Type '${file.type}' not allowed.`;
26 if (file.size > MAX_SIZE) return `File exceeds 10 MB limit.`;
27 return null;
28}
29
30export function uploadFile(
31 file: File,
32 onProgress?: (percent: number) => void
33): Promise<UploadResult> {
34 return new Promise((resolve) => {
35 const error = validateFile(file);
36 if (error) return resolve({ success: false, error });
37
38 const user = auth.currentUser;
39 if (!user) return resolve({ success: false, error: 'Not authenticated.' });
40
41 const filePath = `uploads/${user.uid}/${Date.now()}-${file.name}`;
42 const fileRef = ref(storage, filePath);
43 const task: UploadTask = uploadBytesResumable(fileRef, file, {
44 contentType: file.type,
45 });
46
47 task.on(
48 'state_changed',
49 (snap) => {
50 const pct = Math.round((snap.bytesTransferred / snap.totalBytes) * 100);
51 onProgress?.(pct);
52 },
53 (err) => resolve({ success: false, error: err.message }),
54 async () => {
55 const url = await getDownloadURL(task.snapshot.ref);
56 resolve({ success: true, url, path: filePath });
57 }
58 );
59 });
60}

Common mistakes when uploading Files to Firebase Storage

Why it's a problem: Not configuring Storage security rules, leaving the default deny-all rule in place

How to avoid: Write explicit read and write rules for your upload paths. The default rules deny all access. Enable auth-based access for authenticated users.

Why it's a problem: Using uploadBytes() for large files without progress feedback, causing the UI to appear frozen

How to avoid: Use uploadBytesResumable() for files over 1 MB. It provides progress events, pause/resume, and better error recovery.

Why it's a problem: Forgetting to configure CORS on the Storage bucket, leading to browser errors when fetching files directly

How to avoid: Apply a CORS configuration with gsutil: gsutil cors set cors.json gs://your-bucket. Include your app's domain in the allowed origins.

Why it's a problem: Using the same file name for every upload, causing files to overwrite each other

How to avoid: 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

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I need to upload files to Firebase Storage from a web app using the v9 modular SDK. Show me uploadBytes for simple uploads, uploadBytesResumable with progress tracking, Storage security rules that restrict by auth and file type, client-side validation, and getDownloadURL.

Firebase Prompt

Set up Firebase Storage file uploads with progress tracking. Use uploadBytesResumable, show progress percentage with state_changed listener, write security rules that validate auth, file size under 10 MB, and image/PDF content types. Include TypeScript and CORS config.

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.