To store images in Firebase Storage, create a reference to the target path using ref(), upload the image file with uploadBytes() or uploadBytesResumable() for progress tracking, then retrieve the public download URL with getDownloadURL(). Set up Storage security rules to restrict who can upload and what file types and sizes are allowed. Store the download URL in Firestore alongside your document data to display images in your app.
Uploading and Displaying Images with Firebase Storage
Firebase Storage provides scalable, secure file storage backed by Google Cloud Storage. This tutorial covers the complete image workflow: selecting a file from the user, uploading it to Firebase Storage with progress tracking, retrieving the download URL, storing that URL in Firestore for your app to display, and writing security rules that validate file type, size, and user authentication.
Prerequisites
- A Firebase project with Storage enabled (default bucket created)
- Firebase SDK installed (npm install firebase)
- Authentication configured so users can sign in before uploading
- Basic understanding of file input handling in JavaScript/React
Step-by-step guide
Initialize Firebase Storage and create a file reference
Initialize Firebase Storage and create a file reference
Import the storage functions from firebase/storage and create a reference to where the image will be stored. Organize images in user-scoped paths like users/{uid}/profile.jpg or images/{uid}/{filename} to make security rules simpler. The reference is a pointer to a location in your Storage bucket — no file exists there until you upload one.
1import { getStorage, ref } from 'firebase/storage';2import { getAuth } from 'firebase/auth';34const storage = getStorage();5const auth = getAuth();67// Create a reference to the upload path8function getImageRef(filename: string) {9 const user = auth.currentUser;10 if (!user) throw new Error('Must be signed in to upload');1112 // Store in a user-scoped path13 return ref(storage, `images/${user.uid}/${filename}`);14}Expected result: A StorageReference object pointing to the target path in your Firebase Storage bucket.
Upload an image with progress tracking using uploadBytesResumable
Upload an image with progress tracking using uploadBytesResumable
Use uploadBytesResumable() to upload the image file with progress monitoring. This function returns an UploadTask that emits state_changed events during the upload. Attach a listener to track the upload percentage, handle errors, and run a callback when the upload completes. Include file metadata with the contentType to ensure the file is served with the correct MIME type.
1import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';23async function uploadImage(4 file: File,5 onProgress?: (percent: number) => void6): Promise<string> {7 const user = auth.currentUser;8 if (!user) throw new Error('Not authenticated');910 const storageRef = ref(storage, `images/${user.uid}/${file.name}`);11 const metadata = { contentType: file.type };1213 const uploadTask = uploadBytesResumable(storageRef, file, metadata);1415 return new Promise((resolve, reject) => {16 uploadTask.on(17 'state_changed',18 (snapshot) => {19 const percent = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;20 onProgress?.(Math.round(percent));21 },22 (error) => {23 console.error('Upload failed:', error.code);24 reject(error);25 },26 async () => {27 const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);28 resolve(downloadURL);29 }30 );31 });32}Expected result: The image uploads to Firebase Storage with real-time progress updates and the download URL is returned on completion.
Store the download URL in Firestore
Store the download URL in Firestore
After uploading the image, store the download URL in a Firestore document so your app can display the image later. This is the standard pattern: upload the file to Storage, get the URL, and write it to Firestore. The download URL is a long-lived HTTPS link that includes an access token.
1import { getFirestore, doc, updateDoc, serverTimestamp } from 'firebase/firestore';23const db = getFirestore();45async function uploadProfileImage(file: File): Promise<string> {6 const user = auth.currentUser;7 if (!user) throw new Error('Not authenticated');89 // 1. Upload to Storage10 const downloadURL = await uploadImage(file, (percent) => {11 console.log(`Upload: ${percent}%`);12 });1314 // 2. Store URL in Firestore user profile15 await updateDoc(doc(db, 'users', user.uid), {16 photoURL: downloadURL,17 updatedAt: serverTimestamp()18 });1920 return downloadURL;21}Expected result: The image download URL is stored in the Firestore user profile document and can be used to display the image anywhere in the app.
Write security rules for image uploads
Write security rules for image uploads
Firebase Storage security rules control who can upload, download, and delete files. For image uploads, validate that the user is authenticated, the file is an allowed image type, and the file size is within limits. Use the request.resource object to check incoming file metadata before the upload is allowed to complete.
1rules_version = '2';2service firebase.storage {3 match /b/{bucket}/o {4 match /images/{userId}/{fileName} {5 // Only authenticated users can read images6 allow read: if request.auth != null;78 // Users can only upload to their own folder9 allow write: if request.auth != null10 && request.auth.uid == userId11 && request.resource.size < 5 * 1024 * 1024 // 5 MB max12 && request.resource.contentType.matches('image/.*'); // Images only1314 // Users can delete their own images15 allow delete: if request.auth != null16 && request.auth.uid == userId;17 }18 }19}Expected result: Only authenticated users can upload images to their own folder, with a 5 MB size limit and image-type validation enforced by the rules.
Build a file input component in React
Build a file input component in React
Create a React component that lets users select an image file, previews it before upload, uploads it to Firebase Storage with a progress bar, and displays the final uploaded image. Include client-side validation for file type and size before attempting the upload to provide immediate feedback.
1import { useState, ChangeEvent } from 'react';23export function ImageUploader() {4 const [preview, setPreview] = useState<string | null>(null);5 const [progress, setProgress] = useState(0);6 const [uploadedURL, setUploadedURL] = useState<string | null>(null);7 const [error, setError] = useState<string | null>(null);89 function handleFileSelect(e: ChangeEvent<HTMLInputElement>) {10 const file = e.target.files?.[0];11 if (!file) return;1213 // Client-side validation14 if (!file.type.startsWith('image/')) {15 setError('Please select an image file');16 return;17 }18 if (file.size > 5 * 1024 * 1024) {19 setError('File must be under 5 MB');20 return;21 }2223 setError(null);24 setPreview(URL.createObjectURL(file));25 handleUpload(file);26 }2728 async function handleUpload(file: File) {29 try {30 const url = await uploadImage(file, setProgress);31 setUploadedURL(url);32 } catch (err) {33 setError('Upload failed. Please try again.');34 }35 }3637 return (38 <div>39 <input type="file" accept="image/*" onChange={handleFileSelect} />40 {error && <p style={{ color: 'red' }}>{error}</p>}41 {progress > 0 && progress < 100 && <p>Uploading: {progress}%</p>}42 {preview && <img src={preview} alt="Preview" width={200} />}43 {uploadedURL && <p>Uploaded: {uploadedURL}</p>}44 </div>45 );46}Expected result: A file input with client-side validation, instant preview, upload progress display, and the final download URL shown on completion.
Complete working example
1// Complete Firebase Storage image upload implementation2// Includes upload with progress, download URL, and Firestore integration34import { initializeApp } from 'firebase/app';5import { getAuth } from 'firebase/auth';6import {7 getStorage,8 ref,9 uploadBytesResumable,10 getDownloadURL,11 deleteObject12} from 'firebase/storage';13import {14 getFirestore,15 doc,16 updateDoc,17 serverTimestamp18} from 'firebase/firestore';1920const app = initializeApp({21 apiKey: 'YOUR_API_KEY',22 authDomain: 'YOUR_PROJECT.firebaseapp.com',23 projectId: 'YOUR_PROJECT_ID',24 storageBucket: 'YOUR_PROJECT.appspot.com',25 messagingSenderId: 'YOUR_SENDER_ID',26 appId: 'YOUR_APP_ID'27});2829const auth = getAuth(app);30const storage = getStorage(app);31const db = getFirestore(app);3233const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB34const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];3536// Validate file before upload37function validateImage(file: File): string | null {38 if (!ALLOWED_TYPES.includes(file.type)) {39 return 'File must be JPEG, PNG, WebP, or GIF';40 }41 if (file.size > MAX_FILE_SIZE) {42 return 'File must be under 5 MB';43 }44 return null;45}4647// Upload image with progress tracking48export function uploadImage(49 file: File,50 path: string,51 onProgress?: (percent: number) => void52): Promise<string> {53 const validationError = validateImage(file);54 if (validationError) return Promise.reject(new Error(validationError));5556 const storageRef = ref(storage, path);57 const metadata = { contentType: file.type };58 const task = uploadBytesResumable(storageRef, file, metadata);5960 return new Promise((resolve, reject) => {61 task.on(62 'state_changed',63 (snap) => onProgress?.(Math.round(64 (snap.bytesTransferred / snap.totalBytes) * 10065 )),66 reject,67 async () => resolve(await getDownloadURL(task.snapshot.ref))68 );69 });70}7172// Upload profile image and save URL to Firestore73export async function uploadProfileImage(74 file: File,75 onProgress?: (percent: number) => void76): Promise<string> {77 const user = auth.currentUser;78 if (!user) throw new Error('Not authenticated');7980 const path = `images/${user.uid}/profile_${Date.now()}`;81 const url = await uploadImage(file, path, onProgress);8283 await updateDoc(doc(db, 'users', user.uid), {84 photoURL: url,85 updatedAt: serverTimestamp()86 });8788 return url;89}9091// Delete an image by its storage path92export async function deleteImage(path: string): Promise<void> {93 await deleteObject(ref(storage, path));94}Common mistakes when storing Images in Firebase Storage
Why it's a problem: Not setting contentType metadata during upload, causing the file to be served as application/octet-stream instead of the correct image MIME type
How to avoid: Always pass { contentType: file.type } as metadata to uploadBytes or uploadBytesResumable so the file is served with the correct Content-Type header.
Why it's a problem: Using user-provided filenames directly in storage paths without sanitization, enabling path traversal or filename collisions
How to avoid: Generate unique filenames using UUID or timestamp-based names (e.g., profile_1711234567890.jpg) instead of using the original filename.
Why it's a problem: Not validating file type and size in both client code and security rules, leaving the upload open to abuse
How to avoid: Validate on both sides. Client-side validation provides instant feedback. Server-side security rules are the enforcement layer that cannot be bypassed.
Why it's a problem: Forgetting to configure CORS on the Storage bucket, causing getDownloadURL to fail with cross-origin errors in the browser
How to avoid: Configure CORS on your Storage bucket using gsutil cors set cors.json gs://your-bucket.appspot.com with allowed origins for your domain.
Best practices
- Use user-scoped storage paths (images/{uid}/) so security rules can easily verify ownership
- Validate file type and size on both the client side and in Firebase Storage security rules
- Use uploadBytesResumable for files over 1 MB to provide progress feedback to users
- Store download URLs in Firestore documents rather than constructing them manually
- Generate unique filenames with timestamps or UUIDs to prevent accidental overwrites
- Set contentType metadata on every upload to ensure correct MIME type serving
- Consider using the Resize Images extension to auto-generate thumbnails for uploaded images
- Clean up old images in Storage when users replace their profile picture
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to implement image upload to Firebase Storage in a React app with progress tracking. Show me the upload function with uploadBytesResumable, getDownloadURL, storing the URL in Firestore, and security rules that validate file type and size.
Build a complete image upload system using Firebase Storage with the modular SDK v9+. Include uploadBytesResumable with progress callback, getDownloadURL, Firestore URL persistence, Storage security rules for image validation, and a React component with file input and preview.
Frequently asked questions
What image formats does Firebase Storage support?
Firebase Storage accepts any file format. There are no restrictions on image types. However, your security rules should validate contentType to restrict uploads to image formats your app supports (JPEG, PNG, WebP, GIF, etc.).
What is the maximum file size for Firebase Storage?
The maximum file size is 5 TB per object. However, for image uploads you should enforce a practical limit (e.g., 5-10 MB) in your security rules and client-side validation to prevent abuse and excessive storage costs.
Does the download URL expire?
No. Download URLs generated by getDownloadURL() include an access token that does not expire. The URL remains valid until you revoke the token via the Firebase Console or Admin SDK, or delete the file.
How do I generate image thumbnails automatically?
Install the Resize Images Firebase Extension from the Extensions Hub. It triggers a Cloud Function on each upload that generates resized versions at the dimensions you configure. The thumbnails are saved alongside the original.
Can I upload images without authentication?
Technically yes, if your security rules allow it. However, this is strongly discouraged as it opens your bucket to abuse. Always require authentication and use user-scoped paths for image uploads.
How do I handle CORS errors when loading images from Storage?
Configure CORS on your Storage bucket by creating a cors.json file and applying it with gsutil cors set cors.json gs://your-bucket.appspot.com. Include your app's origin in the allowed origins list.
Can RapidDev help build an image management system with Firebase?
Yes. RapidDev can implement complete image workflows including upload, thumbnail generation, gallery views, image optimization, and CDN delivery using Firebase Storage and Cloud Functions.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation