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
Initialize Firebase Storage and create a reference
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.
1import { initializeApp } from 'firebase/app';2import { getStorage, ref } from 'firebase/storage';34const app = initializeApp(firebaseConfig);5const storage = getStorage(app);67// Create a reference to a file path8const fileRef = ref(storage, `uploads/${userId}/${fileName}`);Expected result: A storage reference object pointing to the specified path, ready for upload operations.
Upload a file with uploadBytes()
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.
1import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';23async function uploadFile(file: File, userId: string) {4 const fileRef = ref(storage, `uploads/${userId}/${Date.now()}-${file.name}`);56 // Upload the file7 const snapshot = await uploadBytes(fileRef, file, {8 contentType: file.type,9 customMetadata: { uploadedBy: userId },10 });1112 // Get the download URL13 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.
Upload with progress tracking using uploadBytesResumable()
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.
1import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';23function uploadWithProgress(4 file: File,5 userId: string,6 onProgress: (percent: number) => void7): 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 });1314 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}2829// Usage with pause/resume30// 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.
Write Storage security rules for authenticated uploads
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.
1// storage.rules2rules_version = '2';3service firebase.storage {4 match /b/{bucket}/o {5 match /uploads/{userId}/{allPaths=**} {6 // Only the file owner can upload and read7 allow read: if request.auth != null8 && request.auth.uid == userId;910 allow write: if request.auth != null11 && request.auth.uid == userId12 // Limit file size to 10 MB13 && request.resource.size < 10 * 1024 * 102414 // Allow only images and PDFs15 && request.resource.contentType.matches('image/.*|application/pdf');1617 allow delete: if request.auth != null18 && 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.
Validate files on the client before uploading
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.
1const ALLOWED_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'application/pdf'];2const MAX_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB34function 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.
Configure CORS for cross-origin access
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.
1// cors.json — save this file locally2[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]1011// Apply with gsutil (run in your terminal)12// gsutil cors set cors.json gs://YOUR_PROJECT.appspot.comExpected result: Browsers can make cross-origin requests to your Storage bucket without CORS errors.
Complete working example
1import {2 getStorage,3 ref,4 uploadBytesResumable,5 getDownloadURL,6 UploadTask,7} from 'firebase/storage';8import { getAuth } from 'firebase/auth';9import { app } from './firebase';1011const storage = getStorage(app);12const auth = getAuth(app);1314const ALLOWED_TYPES = ['image/png', 'image/jpeg', 'image/webp', 'application/pdf'];15const MAX_SIZE = 10 * 1024 * 1024;1617interface UploadResult {18 success: boolean;19 url?: string;20 path?: string;21 error?: string;22}2324function 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}2930export function uploadFile(31 file: File,32 onProgress?: (percent: number) => void33): Promise<UploadResult> {34 return new Promise((resolve) => {35 const error = validateFile(file);36 if (error) return resolve({ success: false, error });3738 const user = auth.currentUser;39 if (!user) return resolve({ success: false, error: 'Not authenticated.' });4041 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 });4647 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.
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.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation