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.