# How to Store Images in Firebase Storage

- Tool: Firebase
- Difficulty: Intermediate
- Time required: 15-20 min
- Compatibility: Firebase Storage (Spark and Blaze plans), firebase v9+ modular SDK
- Last updated: March 2026

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

## Before you start

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

```
import { getStorage, ref } from 'firebase/storage';
import { getAuth } from 'firebase/auth';

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

// Create a reference to the upload path
function getImageRef(filename: string) {
  const user = auth.currentUser;
  if (!user) throw new Error('Must be signed in to upload');

  // Store in a user-scoped path
  return ref(storage, `images/${user.uid}/${filename}`);
}
```

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

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

async function uploadImage(
  file: File,
  onProgress?: (percent: number) => void
): Promise<string> {
  const user = auth.currentUser;
  if (!user) throw new Error('Not authenticated');

  const storageRef = ref(storage, `images/${user.uid}/${file.name}`);
  const metadata = { contentType: file.type };

  const uploadTask = uploadBytesResumable(storageRef, file, metadata);

  return new Promise((resolve, reject) => {
    uploadTask.on(
      'state_changed',
      (snapshot) => {
        const percent = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        onProgress?.(Math.round(percent));
      },
      (error) => {
        console.error('Upload failed:', error.code);
        reject(error);
      },
      async () => {
        const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
        resolve(downloadURL);
      }
    );
  });
}
```

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

```
import { getFirestore, doc, updateDoc, serverTimestamp } from 'firebase/firestore';

const db = getFirestore();

async function uploadProfileImage(file: File): Promise<string> {
  const user = auth.currentUser;
  if (!user) throw new Error('Not authenticated');

  // 1. Upload to Storage
  const downloadURL = await uploadImage(file, (percent) => {
    console.log(`Upload: ${percent}%`);
  });

  // 2. Store URL in Firestore user profile
  await updateDoc(doc(db, 'users', user.uid), {
    photoURL: downloadURL,
    updatedAt: serverTimestamp()
  });

  return downloadURL;
}
```

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

```
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /images/{userId}/{fileName} {
      // Only authenticated users can read images
      allow read: if request.auth != null;

      // Users can only upload to their own folder
      allow write: if request.auth != null
        && request.auth.uid == userId
        && request.resource.size < 5 * 1024 * 1024  // 5 MB max
        && request.resource.contentType.matches('image/.*');  // Images only

      // Users can delete their own images
      allow delete: if request.auth != null
        && request.auth.uid == userId;
    }
  }
}
```

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

```
import { useState, ChangeEvent } from 'react';

export function ImageUploader() {
  const [preview, setPreview] = useState<string | null>(null);
  const [progress, setProgress] = useState(0);
  const [uploadedURL, setUploadedURL] = useState<string | null>(null);
  const [error, setError] = useState<string | null>(null);

  function handleFileSelect(e: ChangeEvent<HTMLInputElement>) {
    const file = e.target.files?.[0];
    if (!file) return;

    // Client-side validation
    if (!file.type.startsWith('image/')) {
      setError('Please select an image file');
      return;
    }
    if (file.size > 5 * 1024 * 1024) {
      setError('File must be under 5 MB');
      return;
    }

    setError(null);
    setPreview(URL.createObjectURL(file));
    handleUpload(file);
  }

  async function handleUpload(file: File) {
    try {
      const url = await uploadImage(file, setProgress);
      setUploadedURL(url);
    } catch (err) {
      setError('Upload failed. Please try again.');
    }
  }

  return (
    <div>
      <input type="file" accept="image/*" onChange={handleFileSelect} />
      {error && <p style={{ color: 'red' }}>{error}</p>}
      {progress > 0 && progress < 100 && <p>Uploading: {progress}%</p>}
      {preview && <img src={preview} alt="Preview" width={200} />}
      {uploadedURL && <p>Uploaded: {uploadedURL}</p>}
    </div>
  );
}
```

**Expected result:** A file input with client-side validation, instant preview, upload progress display, and the final download URL shown on completion.

## Complete code example

File: `image-upload.ts`

```typescript
// Complete Firebase Storage image upload implementation
// Includes upload with progress, download URL, and Firestore integration

import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import {
  getStorage,
  ref,
  uploadBytesResumable,
  getDownloadURL,
  deleteObject
} from 'firebase/storage';
import {
  getFirestore,
  doc,
  updateDoc,
  serverTimestamp
} from 'firebase/firestore';

const app = initializeApp({
  apiKey: 'YOUR_API_KEY',
  authDomain: 'YOUR_PROJECT.firebaseapp.com',
  projectId: 'YOUR_PROJECT_ID',
  storageBucket: 'YOUR_PROJECT.appspot.com',
  messagingSenderId: 'YOUR_SENDER_ID',
  appId: 'YOUR_APP_ID'
});

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

const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5 MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];

// Validate file before upload
function validateImage(file: File): string | null {
  if (!ALLOWED_TYPES.includes(file.type)) {
    return 'File must be JPEG, PNG, WebP, or GIF';
  }
  if (file.size > MAX_FILE_SIZE) {
    return 'File must be under 5 MB';
  }
  return null;
}

// Upload image with progress tracking
export function uploadImage(
  file: File,
  path: string,
  onProgress?: (percent: number) => void
): Promise<string> {
  const validationError = validateImage(file);
  if (validationError) return Promise.reject(new Error(validationError));

  const storageRef = ref(storage, path);
  const metadata = { contentType: file.type };
  const task = uploadBytesResumable(storageRef, file, metadata);

  return new Promise((resolve, reject) => {
    task.on(
      'state_changed',
      (snap) => onProgress?.(Math.round(
        (snap.bytesTransferred / snap.totalBytes) * 100
      )),
      reject,
      async () => resolve(await getDownloadURL(task.snapshot.ref))
    );
  });
}

// Upload profile image and save URL to Firestore
export async function uploadProfileImage(
  file: File,
  onProgress?: (percent: number) => void
): Promise<string> {
  const user = auth.currentUser;
  if (!user) throw new Error('Not authenticated');

  const path = `images/${user.uid}/profile_${Date.now()}`;
  const url = await uploadImage(file, path, onProgress);

  await updateDoc(doc(db, 'users', user.uid), {
    photoURL: url,
    updatedAt: serverTimestamp()
  });

  return url;
}

// Delete an image by its storage path
export async function deleteImage(path: string): Promise<void> {
  await deleteObject(ref(storage, path));
}
```

## Common mistakes

- **Not setting contentType metadata during upload, causing the file to be served as application/octet-stream instead of the correct image MIME type** — undefined Fix: Always pass { contentType: file.type } as metadata to uploadBytes or uploadBytesResumable so the file is served with the correct Content-Type header.
- **Using user-provided filenames directly in storage paths without sanitization, enabling path traversal or filename collisions** — undefined Fix: Generate unique filenames using UUID or timestamp-based names (e.g., profile_1711234567890.jpg) instead of using the original filename.
- **Not validating file type and size in both client code and security rules, leaving the upload open to abuse** — undefined Fix: Validate on both sides. Client-side validation provides instant feedback. Server-side security rules are the enforcement layer that cannot be bypassed.
- **Forgetting to configure CORS on the Storage bucket, causing getDownloadURL to fail with cross-origin errors in the browser** — undefined Fix: 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

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

---

Source: https://www.rapidevelopers.com/firebase-tutorial/how-to-store-images-in-firebase-storage
© RapidDev — https://www.rapidevelopers.com/firebase-tutorial/how-to-store-images-in-firebase-storage
