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

How to Store Images in Firebase Storage

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.

What you'll learn

  • How to upload images with uploadBytes() and uploadBytesResumable()
  • How to track upload progress and handle errors
  • How to retrieve and display images using getDownloadURL()
  • How to write security rules for image uploads
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate9 min read15-20 minFirebase Storage (Spark and Blaze plans), firebase v9+ modular SDKMarch 2026RapidDev Engineering Team
TL;DR

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

1

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.

typescript
1import { getStorage, ref } from 'firebase/storage';
2import { getAuth } from 'firebase/auth';
3
4const storage = getStorage();
5const auth = getAuth();
6
7// Create a reference to the upload path
8function getImageRef(filename: string) {
9 const user = auth.currentUser;
10 if (!user) throw new Error('Must be signed in to upload');
11
12 // Store in a user-scoped path
13 return ref(storage, `images/${user.uid}/${filename}`);
14}

Expected result: A StorageReference object pointing to the target path in your Firebase Storage bucket.

2

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.

typescript
1import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';
2
3async function uploadImage(
4 file: File,
5 onProgress?: (percent: number) => void
6): Promise<string> {
7 const user = auth.currentUser;
8 if (!user) throw new Error('Not authenticated');
9
10 const storageRef = ref(storage, `images/${user.uid}/${file.name}`);
11 const metadata = { contentType: file.type };
12
13 const uploadTask = uploadBytesResumable(storageRef, file, metadata);
14
15 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.

3

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.

typescript
1import { getFirestore, doc, updateDoc, serverTimestamp } from 'firebase/firestore';
2
3const db = getFirestore();
4
5async function uploadProfileImage(file: File): Promise<string> {
6 const user = auth.currentUser;
7 if (!user) throw new Error('Not authenticated');
8
9 // 1. Upload to Storage
10 const downloadURL = await uploadImage(file, (percent) => {
11 console.log(`Upload: ${percent}%`);
12 });
13
14 // 2. Store URL in Firestore user profile
15 await updateDoc(doc(db, 'users', user.uid), {
16 photoURL: downloadURL,
17 updatedAt: serverTimestamp()
18 });
19
20 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.

4

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.

typescript
1rules_version = '2';
2service firebase.storage {
3 match /b/{bucket}/o {
4 match /images/{userId}/{fileName} {
5 // Only authenticated users can read images
6 allow read: if request.auth != null;
7
8 // Users can only upload to their own folder
9 allow write: if request.auth != null
10 && request.auth.uid == userId
11 && request.resource.size < 5 * 1024 * 1024 // 5 MB max
12 && request.resource.contentType.matches('image/.*'); // Images only
13
14 // Users can delete their own images
15 allow delete: if request.auth != null
16 && 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.

5

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.

typescript
1import { useState, ChangeEvent } from 'react';
2
3export 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);
8
9 function handleFileSelect(e: ChangeEvent<HTMLInputElement>) {
10 const file = e.target.files?.[0];
11 if (!file) return;
12
13 // Client-side validation
14 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 }
22
23 setError(null);
24 setPreview(URL.createObjectURL(file));
25 handleUpload(file);
26 }
27
28 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 }
36
37 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

image-upload.ts
1// Complete Firebase Storage image upload implementation
2// Includes upload with progress, download URL, and Firestore integration
3
4import { initializeApp } from 'firebase/app';
5import { getAuth } from 'firebase/auth';
6import {
7 getStorage,
8 ref,
9 uploadBytesResumable,
10 getDownloadURL,
11 deleteObject
12} from 'firebase/storage';
13import {
14 getFirestore,
15 doc,
16 updateDoc,
17 serverTimestamp
18} from 'firebase/firestore';
19
20const 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});
28
29const auth = getAuth(app);
30const storage = getStorage(app);
31const db = getFirestore(app);
32
33const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
34const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
35
36// Validate file before upload
37function 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}
46
47// Upload image with progress tracking
48export function uploadImage(
49 file: File,
50 path: string,
51 onProgress?: (percent: number) => void
52): Promise<string> {
53 const validationError = validateImage(file);
54 if (validationError) return Promise.reject(new Error(validationError));
55
56 const storageRef = ref(storage, path);
57 const metadata = { contentType: file.type };
58 const task = uploadBytesResumable(storageRef, file, metadata);
59
60 return new Promise((resolve, reject) => {
61 task.on(
62 'state_changed',
63 (snap) => onProgress?.(Math.round(
64 (snap.bytesTransferred / snap.totalBytes) * 100
65 )),
66 reject,
67 async () => resolve(await getDownloadURL(task.snapshot.ref))
68 );
69 });
70}
71
72// Upload profile image and save URL to Firestore
73export async function uploadProfileImage(
74 file: File,
75 onProgress?: (percent: number) => void
76): Promise<string> {
77 const user = auth.currentUser;
78 if (!user) throw new Error('Not authenticated');
79
80 const path = `images/${user.uid}/profile_${Date.now()}`;
81 const url = await uploadImage(file, path, onProgress);
82
83 await updateDoc(doc(db, 'users', user.uid), {
84 photoURL: url,
85 updatedAt: serverTimestamp()
86 });
87
88 return url;
89}
90
91// Delete an image by its storage path
92export 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.

ChatGPT Prompt

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.

Firebase Prompt

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.

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.