# Firebase Storage

> |

- **Type:** Skill
- **Install:** `agentstack add skill-kgeminic-claude-skills-1-firebase-storage`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Kgeminic](https://agentstack.voostack.com/s/kgeminic)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Kgeminic](https://github.com/Kgeminic)
- **Source:** https://github.com/Kgeminic/claude-skills-1/tree/main/skills/firebase-storage

## Install

```sh
agentstack add skill-kgeminic-claude-skills-1-firebase-storage
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Firebase Cloud Storage

**Status**: Production Ready
**Last Updated**: 2026-01-25
**Dependencies**: None (standalone skill)
**Latest Versions**: firebase@12.8.0, firebase-admin@13.6.0

---

## Quick Start (5 Minutes)

### 1. Initialize Firebase Storage (Client)

```typescript
// src/lib/firebase.ts
import { initializeApp } from 'firebase/app';
import { getStorage } from 'firebase/storage';

const firebaseConfig = {
  apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY,
  authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN,
  projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
  storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET,
  // ... other config
};

const app = initializeApp(firebaseConfig);
export const storage = getStorage(app);
```

### 2. Initialize Firebase Admin Storage (Server)

```typescript
// src/lib/firebase-admin.ts
import { initializeApp, cert, getApps } from 'firebase-admin/app';
import { getStorage } from 'firebase-admin/storage';

if (!getApps().length) {
  initializeApp({
    credential: cert({
      projectId: process.env.FIREBASE_PROJECT_ID,
      clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
      privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
    }),
    storageBucket: process.env.FIREBASE_STORAGE_BUCKET,
  });
}

export const adminStorage = getStorage().bucket();
```

---

## File Upload (Client SDK)

### Basic Upload

```typescript
import { ref, uploadBytes, getDownloadURL } from 'firebase/storage';
import { storage } from './firebase';

async function uploadFile(file: File, path: string): Promise {
  const storageRef = ref(storage, path);

  // Upload file
  const snapshot = await uploadBytes(storageRef, file);

  // Get download URL
  const downloadURL = await getDownloadURL(snapshot.ref);

  return downloadURL;
}

// Usage
const url = await uploadFile(file, `uploads/${userId}/${file.name}`);
```

### Upload with Progress

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

function uploadFileWithProgress(
  file: File,
  path: string,
  onProgress: (progress: number) => void
): Promise {
  return new Promise((resolve, reject) => {
    const storageRef = ref(storage, path);
    const uploadTask = uploadBytesResumable(storageRef, file);

    uploadTask.on(
      'state_changed',
      (snapshot) => {
        const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
        onProgress(progress);
      },
      (error) => {
        reject(error);
      },
      async () => {
        const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
        resolve(downloadURL);
      }
    );
  });
}

// Usage with React state
const [progress, setProgress] = useState(0);
const url = await uploadFileWithProgress(file, path, setProgress);
```

### Upload with Metadata

```typescript
import { ref, uploadBytes, getDownloadURL, UploadMetadata } from 'firebase/storage';
import { storage } from './firebase';

async function uploadWithMetadata(file: File, path: string) {
  const storageRef = ref(storage, path);

  const metadata: UploadMetadata = {
    contentType: file.type,
    customMetadata: {
      uploadedBy: userId,
      originalName: file.name,
      uploadTime: new Date().toISOString(),
    },
  };

  const snapshot = await uploadBytes(storageRef, file, metadata);
  const downloadURL = await getDownloadURL(snapshot.ref);

  return { downloadURL, metadata: snapshot.metadata };
}
```

### Upload from Data URL / Base64

```typescript
import { ref, uploadString, getDownloadURL } from 'firebase/storage';
import { storage } from './firebase';

// Upload base64 string
async function uploadBase64(base64String: string, path: string) {
  const storageRef = ref(storage, path);

  // For data URL (includes prefix like "data:image/png;base64,")
  const snapshot = await uploadString(storageRef, base64String, 'data_url');

  // For raw base64 (no prefix)
  // const snapshot = await uploadString(storageRef, base64String, 'base64');

  const downloadURL = await getDownloadURL(snapshot.ref);
  return downloadURL;
}
```

---

## File Download

### Get Download URL

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

async function getFileURL(path: string): Promise {
  const storageRef = ref(storage, path);
  const downloadURL = await getDownloadURL(storageRef);
  return downloadURL;
}
```

### Download File as Blob

```typescript
import { ref, getBlob } from 'firebase/storage';
import { storage } from './firebase';

async function downloadFile(path: string): Promise {
  const storageRef = ref(storage, path);
  const blob = await getBlob(storageRef);
  return blob;
}

// Trigger browser download
async function downloadAndSave(path: string, filename: string) {
  const blob = await downloadFile(path);
  const url = URL.createObjectURL(blob);

  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.click();

  URL.revokeObjectURL(url);
}
```

### Get File Metadata

```typescript
import { ref, getMetadata } from 'firebase/storage';
import { storage } from './firebase';

async function getFileMetadata(path: string) {
  const storageRef = ref(storage, path);
  const metadata = await getMetadata(storageRef);

  return {
    name: metadata.name,
    size: metadata.size,
    contentType: metadata.contentType,
    created: metadata.timeCreated,
    updated: metadata.updated,
    customMetadata: metadata.customMetadata,
  };
}
```

---

## File Management

### Delete File

```typescript
import { ref, deleteObject } from 'firebase/storage';
import { storage } from './firebase';

async function deleteFile(path: string): Promise {
  const storageRef = ref(storage, path);
  await deleteObject(storageRef);
}
```

### List Files in Directory

```typescript
import { ref, listAll, list, getDownloadURL } from 'firebase/storage';
import { storage } from './firebase';

// List all files in a directory
async function listAllFiles(directoryPath: string) {
  const storageRef = ref(storage, directoryPath);
  const result = await listAll(storageRef);

  const files = await Promise.all(
    result.items.map(async (itemRef) => ({
      name: itemRef.name,
      fullPath: itemRef.fullPath,
      downloadURL: await getDownloadURL(itemRef),
    }))
  );

  const folders = result.prefixes.map((folderRef) => ({
    name: folderRef.name,
    fullPath: folderRef.fullPath,
  }));

  return { files, folders };
}

// Paginated list (for large directories)
async function listFilesPaginated(directoryPath: string, pageSize = 100) {
  const storageRef = ref(storage, directoryPath);
  const result = await list(storageRef, { maxResults: pageSize });

  // Get next page
  if (result.nextPageToken) {
    const nextPage = await list(storageRef, {
      maxResults: pageSize,
      pageToken: result.nextPageToken,
    });
  }

  return result;
}
```

### Update Metadata

```typescript
import { ref, updateMetadata } from 'firebase/storage';
import { storage } from './firebase';

async function updateFileMetadata(path: string, newMetadata: object) {
  const storageRef = ref(storage, path);
  const updatedMetadata = await updateMetadata(storageRef, {
    customMetadata: newMetadata,
  });
  return updatedMetadata;
}
```

---

## Server-Side Operations (Admin SDK)

### Upload from Server

```typescript
import { adminStorage } from './firebase-admin';

async function uploadFromServer(
  buffer: Buffer,
  destination: string,
  contentType: string
) {
  const file = adminStorage.file(destination);

  await file.save(buffer, {
    contentType,
    metadata: {
      metadata: {
        uploadedBy: 'server',
        uploadTime: new Date().toISOString(),
      },
    },
  });

  // Make file publicly accessible (if needed)
  await file.makePublic();

  // Get public URL
  const publicUrl = `https://storage.googleapis.com/${adminStorage.name}/${destination}`;

  return publicUrl;
}
```

### Generate Signed URL

```typescript
import { adminStorage } from './firebase-admin';

async function generateSignedUrl(
  path: string,
  expiresInMinutes = 60
): Promise {
  const file = adminStorage.file(path);

  const [signedUrl] = await file.getSignedUrl({
    action: 'read',
    expires: Date.now() + expiresInMinutes * 60 * 1000,
  });

  return signedUrl;
}

// For uploads (write access)
async function generateUploadUrl(path: string): Promise {
  const file = adminStorage.file(path);

  const [signedUrl] = await file.getSignedUrl({
    action: 'write',
    expires: Date.now() + 15 * 60 * 1000, // 15 minutes
    contentType: 'application/octet-stream',
  });

  return signedUrl;
}
```

### Delete from Server

```typescript
import { adminStorage } from './firebase-admin';

async function deleteFromServer(path: string): Promise {
  const file = adminStorage.file(path);
  await file.delete();
}

// Delete entire directory
async function deleteDirectory(directoryPath: string): Promise {
  await adminStorage.deleteFiles({
    prefix: directoryPath,
  });
}
```

---

## Security Rules

### Basic Rules Structure

```javascript
// storage.rules
rules_version = '2';

service firebase.storage {
  match /b/{bucket}/o {

    // Helper functions
    function isAuthenticated() {
      return request.auth != null;
    }

    function isOwner(userId) {
      return request.auth.uid == userId;
    }

    function isValidImage() {
      return request.resource.contentType.matches('image/.*')
        && request.resource.size  void;
  accept?: string;
  maxSize?: number; // in bytes
}

export function FileUpload({
  path,
  onUploadComplete,
  accept = 'image/*',
  maxSize = 5 * 1024 * 1024, // 5MB
}: FileUploadProps) {
  const [uploading, setUploading] = useState(false);
  const [progress, setProgress] = useState(0);
  const [error, setError] = useState(null);
  const inputRef = useRef(null);

  const handleFileChange = async (e: React.ChangeEvent) => {
    const file = e.target.files?.[0];
    if (!file) return;

    // Validate file size
    if (file.size > maxSize) {
      setError(`File size must be less than ${maxSize / 1024 / 1024}MB`);
      return;
    }

    setError(null);
    setUploading(true);
    setProgress(0);

    try {
      const storageRef = ref(storage, `${path}/${Date.now()}-${file.name}`);
      const uploadTask = uploadBytesResumable(storageRef, file);

      uploadTask.on(
        'state_changed',
        (snapshot) => {
          const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
          setProgress(progress);
        },
        (error) => {
          setError(error.message);
          setUploading(false);
        },
        async () => {
          const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
          onUploadComplete(downloadURL);
          setUploading(false);
          setProgress(100);
        }
      );
    } catch (err: any) {
      setError(err.message);
      setUploading(false);
    }
  };

  return (
    
      
       inputRef.current?.click()}
        disabled={uploading}
        className="px-4 py-2 bg-blue-500 text-white rounded disabled:opacity-50"
      >
        {uploading ? `Uploading... ${Math.round(progress)}%` : 'Upload File'}
      
      {error && {error}}
    
  );
}
```

### Image Preview with Upload

```typescript
// components/ImageUpload.tsx
'use client';

import { useState } from 'react';
import { ref, uploadBytesResumable, getDownloadURL } from 'firebase/storage';
import { storage } from '@/lib/firebase';
import Image from 'next/image';

interface ImageUploadProps {
  currentImage?: string;
  path: string;
  onUploadComplete: (url: string) => void;
}

export function ImageUpload({
  currentImage,
  path,
  onUploadComplete,
}: ImageUploadProps) {
  const [preview, setPreview] = useState(currentImage || null);
  const [uploading, setUploading] = useState(false);
  const [progress, setProgress] = useState(0);

  const handleFileChange = async (e: React.ChangeEvent) => {
    const file = e.target.files?.[0];
    if (!file) return;

    // Show preview immediately
    const reader = new FileReader();
    reader.onloadend = () => {
      setPreview(reader.result as string);
    };
    reader.readAsDataURL(file);

    // Upload
    setUploading(true);
    const storageRef = ref(storage, `${path}/${Date.now()}-${file.name}`);
    const uploadTask = uploadBytesResumable(storageRef, file);

    uploadTask.on(
      'state_changed',
      (snapshot) => {
        setProgress((snapshot.bytesTransferred / snapshot.totalBytes) * 100);
      },
      (error) => {
        console.error('Upload error:', error);
        setUploading(false);
      },
      async () => {
        const downloadURL = await getDownloadURL(uploadTask.snapshot.ref);
        onUploadComplete(downloadURL);
        setUploading(false);
      }
    );
  };

  return (
    
      {preview ? (
        
      ) : (
        
          No image
        
      )}

      
        
        {uploading ? `${Math.round(progress)}%` : '+'}
      
    
  );
}
```

---

## Error Handling

### Common Storage Errors

| Error Code | Description | Solution |
|------------|-------------|----------|
| `storage/unauthorized` | User not allowed to access | Check security rules, ensure user authenticated |
| `storage/canceled` | Upload/download canceled | Handle gracefully, allow retry |
| `storage/unknown` | Unknown error | Check network, retry with backoff |
| `storage/object-not-found` | File doesn't exist | Verify path, handle missing files |
| `storage/bucket-not-found` | Bucket doesn't exist | Check storageBucket config |
| `storage/quota-exceeded` | Storage quota exceeded | Upgrade plan or delete files |
| `storage/unauthenticated` | User not authenticated | Sign in user first |
| `storage/invalid-checksum` | File corrupted during upload | Retry upload |
| `storage/retry-limit-exceeded` | Too many retries | Check network, try later |

### Error Handler Utility

```typescript
export function getStorageErrorMessage(error: any): string {
  const messages: Record = {
    'storage/unauthorized': 'You do not have permission to access this file',
    'storage/canceled': 'Upload was canceled',
    'storage/object-not-found': 'File not found',
    'storage/quota-exceeded': 'Storage quota exceeded',
    'storage/unauthenticated': 'Please sign in to upload files',
    'storage/invalid-checksum': 'Upload failed. Please try again',
    'storage/retry-limit-exceeded': 'Upload failed after multiple retries',
  };

  return messages[error.code] || 'An unexpected error occurred';
}
```

---

## Known Issues Prevention

This skill prevents **9** documented Firebase Storage errors:

| Issue # | Error/Issue | Description | How to Avoid | Source |
|---------|-------------|-------------|--------------|--------|
| **#1** | `storage/unauthorized` | Security rules blocking access | Test rules, ensure user authenticated | Common |
| **#2** | CORS errors | Browser blocked cross-origin request | Configure CORS with `gsutil cors set` | [Docs](https://firebase.google.com/docs/storage/web/download-files#cors_configuration) |
| **#3** | Large file timeout | Upload times out | Use `uploadBytesResumable` for large files | Common |
| **#4** | Memory issues | Loading large file into memory | Stream large files, use signed URLs | Common |
| **#5** | Missing content type | File served with wrong MIME type | Always set `contentType` in metadata | Common |
| **#6** | URL expiration | Download URL stops working | Regenerate URLs, use signed URLs for temp access | Common |
| **#7** | `storage/quota-exceeded` | Free tier limit reached | Monitor usage, upgrade plan | Common |
| **#8** | Private key newline issue | Admin SDK fails to initialize | Use `.replace(/\\n/g, '\n')` | Common |
| **#9** | Duplicate uploads | Same file uploaded multiple times | Add timestamp/UUID to filename | Best practice |

---

## Best Practices

### Always Do

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Kgeminic](https://github.com/Kgeminic)
- **Source:** [Kgeminic/claude-skills-1](https://github.com/Kgeminic/claude-skills-1)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** yes
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-kgeminic-claude-skills-1-firebase-storage
- Seller: https://agentstack.voostack.com/s/kgeminic
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
