Back to blog
InspectionJanuary 1, 2024ยท 7 min read

Browser Storage Limits: How Much Data Can Websites Really Store?

A practical Fusebox guide to browser storage limits.

Browser Storage Limits: How Much Data Can Websites Really Store?

Published: January 2024
Reading time: 7 minutes

Websites can store gigabytes of data in your browser. But how much exactly? What happens when limits are reached? Here's everything developers need to know about browser storage quotas.

Storage Types and Their Limits

The Storage Hierarchy

// From smallest to largest:
1. Cookies: 4KB per cookie, ~50 cookies per domain
2. sessionStorage: 5-10MB
3. localStorage: 5-10MB  
4. IndexedDB: 50MB to... unlimited?
5. Cache API: Same as IndexedDB
6. WebSQL: Deprecated, don't use

Quick Storage Audit

1. Check Current Usage

// See how much storage a site is using
async function checkStorageUsage() {
  if ('storage' in navigator && 'estimate' in navigator.storage) {
    const estimate = await navigator.storage.estimate();
    
    console.log('๐Ÿ“Š Storage Usage Report:');
    console.log(`Used: ${(estimate.usage / 1024 / 1024).toFixed(2)} MB`);
    console.log(`Quota: ${(estimate.quota / 1024 / 1024).toFixed(2)} MB`);
    console.log(`Percentage: ${(estimate.usage / estimate.quota * 100).toFixed(2)}%`);
    
    // Break down by type (Chrome only)
    if (estimate.usageDetails) {
      console.log('\nBreakdown:');
      Object.entries(estimate.usageDetails).forEach(([type, bytes]) => {
        console.log(`  ${type}: ${(bytes / 1024 / 1024).toFixed(2)} MB`);
      });
    }
  } else {
    console.log('Storage API not supported');
  }
}

checkStorageUsage();

2. Detailed Storage Analysis

// Comprehensive storage audit
async function auditAllStorage() {
  const report = {
    cookies: 0,
    localStorage: 0,
    sessionStorage: 0,
    indexedDB: 0,
    cacheStorage: 0,
    total: 0
  };
  
  // 1. Cookies
  const cookieSize = document.cookie.length;
  report.cookies = cookieSize;
  console.log(`๐Ÿช Cookies: ${cookieSize} bytes`);
  
  // 2. localStorage
  let localStorageSize = 0;
  for (let key in localStorage) {
    localStorageSize += key.length + localStorage.getItem(key).length;
  }
  report.localStorage = localStorageSize;
  console.log(`๐Ÿ’พ localStorage: ${(localStorageSize / 1024).toFixed(2)} KB`);
  
  // 3. sessionStorage
  let sessionStorageSize = 0;
  for (let key in sessionStorage) {
    sessionStorageSize += key.length + sessionStorage.getItem(key).length;
  }
  report.sessionStorage = sessionStorageSize;
  console.log(`๐Ÿ“‹ sessionStorage: ${(sessionStorageSize / 1024).toFixed(2)} KB`);
  
  // 4. IndexedDB (estimation)
  if ('storage' in navigator) {
    const estimate = await navigator.storage.estimate();
    // This includes IndexedDB + Cache API
    const idbAndCache = estimate.usage - localStorageSize - sessionStorageSize - cookieSize;
    console.log(`๐Ÿ—„๏ธ IndexedDB + Cache: ${(idbAndCache / 1024 / 1024).toFixed(2)} MB`);
    report.indexedDB = idbAndCache;
  }
  
  report.total = Object.values(report).reduce((a, b) => a + b, 0);
  console.log(`\n๐Ÿ“Š Total: ${(report.total / 1024 / 1024).toFixed(2)} MB`);
  
  return report;
}

Browser-Specific Limits

Chrome/Edge (Chromium)

// Dynamic quota based on available disk space
const getChromeQuota = async () => {
  const estimate = await navigator.storage.estimate();
  
  // Chrome gives up to 60% of free disk space
  // Example: 100GB free = 60GB quota per origin
  
  console.log('Chrome Storage Limits:');
  console.log('localStorage: 10MB');
  console.log('sessionStorage: 10MB');
  console.log(`IndexedDB: ${(estimate.quota / 1024 / 1024 / 1024).toFixed(2)} GB`);
  console.log('Cookies: 180 per domain');
};

Firefox

// Firefox Group Limit
// Total storage per origin group: 2GB default
// Can be increased with permission

const firefoxLimits = {
  localStorage: '10MB',
  sessionStorage: '10MB',
  indexedDB: '2GB (default), 50GB+ (with permission)',
  cookies: '150 per domain'
};

Safari

// Safari is more restrictive
const safariLimits = {
  localStorage: '5MB',
  sessionStorage: '5MB',
  indexedDB: '1GB initially, can request more',
  cookies: 'Unlimited but ITP deletes after 7 days'
};

// Safari requires user interaction for > 1GB
async function requestMoreStorage() {
  if ('storage' in navigator && 'persist' in navigator.storage) {
    const isPersisted = await navigator.storage.persist();
    console.log(`Persistent storage: ${isPersisted ? 'granted' : 'denied'}`);
  }
}

Storage Pressure and Eviction

How Browsers Free Space

// LRU (Least Recently Used) Eviction
// When disk space is low, browsers delete:
// 1. Temporary storage (not persisted)
// 2. Oldest origin first
// 3. Entire origin at once (all or nothing)

async function checkPersistence() {
  if ('storage' in navigator && 'persisted' in navigator.storage) {
    const isPersisted = await navigator.storage.persisted();
    
    if (!isPersisted) {
      console.warn('โš ๏ธ Storage can be evicted when space is low');
      
      // Request persistence
      const granted = await navigator.storage.persist();
      console.log(`Persistence ${granted ? 'granted' : 'denied'}`);
    } else {
      console.log('โœ… Storage is persistent');
    }
  }
}

Eviction Policies by Browser

Chrome:

// Evicts when < 10% disk space remains
// LRU eviction of non-persistent origins
// Persistent storage only with permission

Firefox:

// Evicts when < 5% disk space remains
// Prioritizes based on usage patterns
// Respects user bookmarks/pins

Safari:

// More aggressive eviction
// Non-persistent storage after 7 days
// Requires user interaction for persistence

Real-World Storage Examples

Example 1: Progressive Web App

// PWA storing offline data
{
  "Service Worker Cache": "145MB", // Cached assets
  "IndexedDB": {
    "user_data": "2.3MB",        // User preferences
    "offline_articles": "89MB",   // Offline reading
    "sync_queue": "0.5MB"        // Pending uploads
  },
  "localStorage": "45KB",         // Settings
  "Total": "237MB"
}

// Strategy: Request persistent storage
navigator.storage.persist().then(granted => {
  if (granted) {
    // Safe to store critical data
    cacheOfflineContent();
  }
});

Example 2: Email Client

// Heavy IndexedDB usage
{
  "IndexedDB": {
    "emails": "1.8GB",          // Message bodies
    "attachments": "3.2GB",     // File storage
    "search_index": "234MB",    // Full-text search
    "contacts": "12MB"          // Address book
  },
  "Cache API": "156MB",         // UI assets
  "Total": "5.4GB"
}

// Problem: Approaching Safari limit
// Solution: Implement storage management

Example 3: Video Streaming

// Netflix-style offline viewing
async function downloadVideo(videoId) {
  // Check available space first
  const estimate = await navigator.storage.estimate();
  const available = estimate.quota - estimate.usage;
  const videoSize = 800 * 1024 * 1024; // 800MB
  
  if (available < videoSize) {
    // Need to free space
    await cleanOldDownloads();
  }
  
  // Store in Cache API
  const cache = await caches.open('offline-videos');
  await cache.add(`/video/${videoId}`);
}

Storage Management Strategies

1. Implement Storage Quotas

class StorageManager {
  constructor(maxSize = 100 * 1024 * 1024) { // 100MB default
    this.maxSize = maxSize;
  }
  
  async checkQuota() {
    const estimate = await navigator.storage.estimate();
    const used = estimate.usage;
    
    if (used > this.maxSize) {
      await this.cleanup();
    }
  }
  
  async cleanup() {
    // Remove oldest data first
    const db = await openDB();
    const transaction = db.transaction(['cache'], 'readwrite');
    const store = transaction.objectStore('cache');
    
    // Get all entries with timestamps
    const entries = await store.index('timestamp').getAll();
    
    // Delete oldest until under quota
    let deleted = 0;
    for (const entry of entries) {
      await store.delete(entry.id);
      deleted += entry.size;
      
      if (await this.getCurrentUsage() < this.maxSize * 0.8) {
        break; // Leave 20% buffer
      }
    }
    
    console.log(`Cleaned up ${(deleted / 1024 / 1024).toFixed(2)} MB`);
  }
}

2. User-Controlled Storage

// Let users manage their storage
async function showStorageUI() {
  const usage = await calculateStorageUsage();
  
  const ui = `
    <div class="storage-manager">
      <h3>Storage Usage</h3>
      <div class="storage-bar">
        <div class="used" style="width: ${usage.percentage}%"></div>
      </div>
      <p>${usage.usedMB} MB of ${usage.quotaMB} MB used</p>
      
      <h4>Clear Data:</h4>
      <label>
        <input type="checkbox" value="cache"> 
        Offline content (${usage.cacheMB} MB)
      </label>
      <label>
        <input type="checkbox" value="downloads"> 
        Downloads (${usage.downloadsMB} MB)
      </label>
      <button onclick="clearSelected()">Clear Selected</button>
    </div>
  `;
  
  document.getElementById('storage-ui').innerHTML = ui;
}

3. Smart Caching

// Intelligent cache management
class SmartCache {
  async store(key, data, priority = 'normal') {
    const size = new Blob([JSON.stringify(data)]).size;
    
    // Check if we have space
    const estimate = await navigator.storage.estimate();
    const available = estimate.quota - estimate.usage;
    
    if (size > available) {
      // Free space based on priority
      await this.evictLowPriority(size);
    }
    
    // Store with metadata
    const db = await this.openDB();
    await db.put('cache', {
      key,
      data,
      size,
      priority,
      timestamp: Date.now(),
      lastAccessed: Date.now()
    });
  }
  
  async evictLowPriority(needed) {
    const db = await this.openDB();
    const tx = db.transaction('cache', 'readwrite');
    
    // Get low priority items
    const items = await tx.store.index('priority').getAll('low');
    
    let freed = 0;
    for (const item of items) {
      await tx.store.delete(item.key);
      freed += item.size;
      
      if (freed >= needed) break;
    }
  }
}

Testing Storage Limits

1. Fill Storage Test

// Test actual storage limits
async function testStorageLimit() {
  console.log('Testing storage limits...');
  
  const chunkSize = 1024 * 1024; // 1MB chunks
  const chunks = [];
  
  try {
    while (true) {
      const chunk = new Array(chunkSize).fill('x').join('');
      localStorage.setItem(`test_${chunks.length}`, chunk);
      chunks.push(`test_${chunks.length}`);
      
      if (chunks.length % 5 === 0) {
        console.log(`Stored ${chunks.length} MB`);
      }
    }
  } catch (e) {
    console.log(`Storage full at ${chunks.length} MB`);
    console.log('Error:', e.name);
    
    // Cleanup
    chunks.forEach(key => localStorage.removeItem(key));
  }
}

2. Quota Monitoring

// Monitor storage usage over time
class QuotaMonitor {
  constructor() {
    this.history = [];
    this.interval = null;
  }
  
  start() {
    this.interval = setInterval(async () => {
      const estimate = await navigator.storage.estimate();
      
      this.history.push({
        timestamp: Date.now(),
        usage: estimate.usage,
        quota: estimate.quota,
        percentage: (estimate.usage / estimate.quota * 100).toFixed(2)
      });
      
      // Alert if approaching limit
      if (estimate.usage / estimate.quota > 0.9) {
        console.warn('โš ๏ธ Storage usage above 90%');
        this.onHighUsage?.(estimate);
      }
      
      // Keep last hour of data
      const hourAgo = Date.now() - 3600000;
      this.history = this.history.filter(h => h.timestamp > hourAgo);
    }, 60000); // Check every minute
  }
  
  getReport() {
    const latest = this.history[this.history.length - 1];
    const oldest = this.history[0];
    
    return {
      current: latest,
      trend: latest.usage - oldest.usage,
      averageUsage: this.history.reduce((sum, h) => sum + h.usage, 0) / this.history.length
    };
  }
}

Best Practices

1. Always Check Before Storing

async function safeStore(key, data) {
  const size = new Blob([JSON.stringify(data)]).size;
  const estimate = await navigator.storage.estimate();
  
  if (estimate.usage + size > estimate.quota * 0.95) {
    throw new Error('Insufficient storage space');
  }
  
  // Safe to store
  await storeData(key, data);
}

2. Implement Graceful Degradation

class ResilientStorage {
  async store(key, value) {
    // Try IndexedDB first
    try {
      await this.storeIndexedDB(key, value);
      return 'indexeddb';
    } catch (e) {
      console.warn('IndexedDB failed:', e);
    }
    
    // Fall back to localStorage
    try {
      localStorage.setItem(key, JSON.stringify(value));
      return 'localstorage';
    } catch (e) {
      console.warn('localStorage failed:', e);
    }
    
    // Last resort: session only
    try {
      sessionStorage.setItem(key, JSON.stringify(value));
      return 'session';
    } catch (e) {
      console.error('All storage methods failed');
      throw e;
    }
  }
}

3. Respect User's Device

// Check device storage before heavy caching
async function shouldCacheOffline() {
  const estimate = await navigator.storage.estimate();
  const quotaGB = estimate.quota / 1024 / 1024 / 1024;
  
  // Don't cache on devices with < 4GB quota
  if (quotaGB < 4) {
    return false;
  }
  
  // Check connection type
  const connection = navigator.connection;
  if (connection?.saveData) {
    return false; // User wants to save data
  }
  
  return true;
}

Storage Quota Checklist

Monitor

  • Track storage usage over time
  • Alert before hitting limits
  • Log eviction events
  • Monitor quota changes

Optimize

  • Compress data before storing
  • Remove duplicate data
  • Implement data expiration
  • Use appropriate storage type

User Experience

  • Show storage usage to users
  • Provide clear data options
  • Handle quota errors gracefully
  • Respect data saver preferences

The Bottom Line

Browser storage is powerful but not unlimited:

  • Know your limits - They vary by browser
  • Monitor usage - Don't hit limits unexpectedly
  • Plan for eviction - Data can be deleted
  • Respect users - Their device, their rules

Build resilient storage strategies that work within limits.


Monitor storage quotas instantly: Fusebox shows real-time storage usage, quotas, and what websites store in your browser. Understand storage impact immediately. $29 one-time purchase.