Storage and Persistence APIs

1. localStorage and sessionStorage Operations

Method Syntax Description Scope
setItem storage.setItem(key, value) Stores key-value pair. Value converted to string. Throws QuotaExceededError if storage full. Both
getItem storage.getItem(key) Retrieves value by key. Returns null if key doesn't exist. Both
removeItem storage.removeItem(key) Removes key-value pair. No error if key doesn't exist. Both
clear storage.clear() Removes all key-value pairs from storage. Both
key storage.key(index) Returns key name at index position. Returns null if out of range. Both
length storage.length Number of stored key-value pairs. Read-only property. Both
Storage Type Lifetime Capacity Use Case
localStorage Persists until explicitly deleted. Survives browser restart. ~5-10MB per origin User preferences, settings, cached data
sessionStorage Cleared when page session ends (tab/window closed). Survives page reload. ~5-10MB per origin Temporary data, form state, single-session cache
Event Property Type Description
storage event StorageEvent Fires on other tabs/windows when storage changes. Doesn't fire in tab that made change.
key string Key that was changed. null if clear() called.
oldValue string Previous value. null if new key.
newValue string New value. null if item removed.
url string URL of page that made the change.
storageArea Storage Reference to localStorage or sessionStorage affected.

Example: Basic storage operations

// Store data
localStorage.setItem("username", "john_doe");
localStorage.setItem("theme", "dark");

// Store objects (must stringify)
const user = { "id": 123, "name": "John" };
localStorage.setItem("user", JSON.stringify(user));

// Retrieve data
const username = localStorage.getItem("username");
console.log(username); // "john_doe"

// Retrieve and parse objects
const userStr = localStorage.getItem("user");
const userData = JSON.parse(userStr);
console.log(userData.name); // "John"

// Remove item
localStorage.removeItem("theme");

// Clear all
localStorage.clear();

// Check if key exists
if (localStorage.getItem("username") !== null) {
  console.log("Username exists");
}

// Iterate all keys
for (let i = 0; i < localStorage.length; i++) {
  const key = localStorage.key(i);
  const value = localStorage.getItem(key);
  console.log(`${key}: ${value}`);
}

Example: Helper functions and patterns

// Safe JSON storage wrapper
const storage = {
  "set": (key, value) => {
    try {
      localStorage.setItem(key, JSON.stringify(value));
      return true;
    } catch (error) {
      console.error("Storage error:", error);
      return false;
    }
  },
  "get": (key, defaultValue = null) => {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : defaultValue;
    } catch (error) {
      return defaultValue;
    }
  },
  "remove": (key) => {
    localStorage.removeItem(key);
  }
};

// Usage
storage.set("settings", { "theme": "dark", "lang": "en" });
const settings = storage.get("settings", {});

// Storage with expiration
function setWithExpiry(key, value, ttl) {
  const item = {
    "value": value,
    "expiry": Date.now() + ttl
  };
  localStorage.setItem(key, JSON.stringify(item));
}

function getWithExpiry(key) {
  const itemStr = localStorage.getItem(key);
  if (!itemStr) return null;
  
  const item = JSON.parse(itemStr);
  if (Date.now() > item.expiry) {
    localStorage.removeItem(key);
    return null;
  }
  return item.value;
}

// Set with 1 hour expiry
setWithExpiry("token", "abc123", 3600000);

Example: Storage event for cross-tab communication

// Listen for storage changes from other tabs
window.addEventListener("storage", (event) => {
  console.log("Storage changed in another tab");
  console.log("Key:", event.key);
  console.log("Old value:", event.oldValue);
  console.log("New value:", event.newValue);
  console.log("URL:", event.url);
  console.log("Storage area:", event.storageArea === localStorage);
  
  // React to specific changes
  if (event.key === "theme") {
    applyTheme(event.newValue);
  }
  
  // Handle logout in all tabs
  if (event.key === "isLoggedIn" && event.newValue === "false") {
    redirectToLogin();
  }
});

// Trigger storage event in other tabs
localStorage.setItem("notification", JSON.stringify({
  "message": "New update available",
  "timestamp": Date.now()
}));
Note: Web Storage only stores strings - use JSON.stringify() and JSON.parse() for objects. Storage is synchronous and can block UI - use IndexedDB for large data. Data is scoped to origin (protocol + domain + port). Storage event doesn't fire in the tab that made the change.
Warning: Storage can throw QuotaExceededError when full - always wrap in try/catch. Private browsing modes may have reduced or disabled storage. Never store sensitive data (passwords, tokens) in localStorage - vulnerable to XSS. Use sessionStorage for temporary data that shouldn't persist.

2. IndexedDB Database Operations and Transactions

Method Syntax Description Browser Support
open indexedDB.open(name, version) Opens database. Returns IDBOpenDBRequest. Triggers upgradeneeded if version changes. All Browsers
deleteDatabase indexedDB.deleteDatabase(name) Deletes entire database. Returns IDBOpenDBRequest. All Browsers
databases NEW indexedDB.databases() Returns Promise with array of available databases and versions. Modern Browsers
IDBDatabase Method Description Use Case
createObjectStore Creates object store (table). Only in upgradeneeded event. Options: keyPath, autoIncrement. Database schema creation
deleteObjectStore Deletes object store. Only in upgradeneeded event. Schema migration
transaction Creates transaction. Parameters: storeNames (string/array), mode ("readonly"/"readwrite"). Access data
close Closes database connection. Good practice when done. Cleanup
IDBObjectStore Method Description Returns
add Adds new record. Fails if key exists. Returns IDBRequest. IDBRequest
put Adds or updates record. Overwrites if key exists. IDBRequest
get Gets record by key. Returns undefined if not found. IDBRequest
getAll Gets all records. Optional query and count parameters. IDBRequest
delete Deletes record by key or key range. IDBRequest
clear Deletes all records in object store. IDBRequest
count Counts records. Optional key or key range parameter. IDBRequest
openCursor Opens cursor for iteration. Optional query and direction. IDBRequest
createIndex Creates index for queries. Only in upgradeneeded event. IDBIndex
index Gets existing index for querying. IDBIndex

Example: Opening database and creating schema

// Open database (creates if doesn't exist)
const request = indexedDB.open("MyDatabase", 1);

// Handle errors
request.onerror = (event) => {
  console.error("Database error:", event.target.error);
};

// Create schema (only runs when version changes)
request.onupgradeneeded = (event) => {
  const db = event.target.result;
  
  // Create object store with auto-incrementing key
  const userStore = db.createObjectStore("users", {
    "keyPath": "id",
    "autoIncrement": true
  });
  
  // Create indexes for searching
  userStore.createIndex("email", "email", { "unique": true });
  userStore.createIndex("age", "age", { "unique": false });
  userStore.createIndex("city", "city", { "unique": false });
  
  // Create another object store
  const productStore = db.createObjectStore("products", {
    "keyPath": "sku"
  });
  
  productStore.createIndex("category", "category");
  productStore.createIndex("price", "price");
};

// Success - database ready
request.onsuccess = (event) => {
  const db = event.target.result;
  console.log("Database opened successfully");
  
  // Store db reference for later use
  window.db = db;
};

Example: CRUD operations

// Add data
function addUser(userData) {
  const transaction = db.transaction(["users"], "readwrite");
  const store = transaction.objectStore("users");
  const request = store.add(userData);
  
  request.onsuccess = () => {
    console.log("User added:", request.result); // Returns key
  };
  
  request.onerror = () => {
    console.error("Add error:", request.error);
  };
}

addUser({ "name": "John", "email": "john@example.com", "age": 30 });

// Get data by key
function getUser(id) {
  const transaction = db.transaction(["users"], "readonly");
  const store = transaction.objectStore("users");
  const request = store.get(id);
  
  request.onsuccess = () => {
    if (request.result) {
      console.log("User found:", request.result);
    } else {
      console.log("User not found");
    }
  };
}

getUser(1);

// Update data (put overwrites)
function updateUser(userData) {
  const transaction = db.transaction(["users"], "readwrite");
  const store = transaction.objectStore("users");
  const request = store.put(userData); // Use put for update
  
  request.onsuccess = () => {
    console.log("User updated");
  };
}

updateUser({ "id": 1, "name": "John Doe", "email": "john@example.com", "age": 31 });

// Delete data
function deleteUser(id) {
  const transaction = db.transaction(["users"], "readwrite");
  const store = transaction.objectStore("users");
  const request = store.delete(id);
  
  request.onsuccess = () => {
    console.log("User deleted");
  };
}

deleteUser(1);

// Get all data
function getAllUsers() {
  const transaction = db.transaction(["users"], "readonly");
  const store = transaction.objectStore("users");
  const request = store.getAll();
  
  request.onsuccess = () => {
    console.log("All users:", request.result);
  };
}

getAllUsers();

Example: Indexes and cursors

// Query by index
function getUserByEmail(email) {
  const transaction = db.transaction(["users"], "readonly");
  const store = transaction.objectStore("users");
  const index = store.index("email");
  const request = index.get(email);
  
  request.onsuccess = () => {
    console.log("User:", request.result);
  };
}

getUserByEmail("john@example.com");

// Range queries with IDBKeyRange
function getUsersByAge(minAge, maxAge) {
  const transaction = db.transaction(["users"], "readonly");
  const store = transaction.objectStore("users");
  const index = store.index("age");
  const range = IDBKeyRange.bound(minAge, maxAge);
  const request = index.getAll(range);
  
  request.onsuccess = () => {
    console.log("Users in age range:", request.result);
  };
}

getUsersByAge(25, 35);

// Cursor for iteration
function iterateUsers() {
  const transaction = db.transaction(["users"], "readonly");
  const store = transaction.objectStore("users");
  const request = store.openCursor();
  
  request.onsuccess = (event) => {
    const cursor = event.target.result;
    if (cursor) {
      console.log("User:", cursor.value);
      cursor.continue(); // Move to next record
    } else {
      console.log("Done iterating");
    }
  };
}

iterateUsers();

// Cursor with filtering
function filterUsers(callback) {
  const transaction = db.transaction(["users"], "readonly");
  const store = transaction.objectStore("users");
  const request = store.openCursor();
  const results = [];
  
  request.onsuccess = (event) => {
    const cursor = event.target.result;
    if (cursor) {
      if (callback(cursor.value)) {
        results.push(cursor.value);
      }
      cursor.continue();
    } else {
      console.log("Filtered results:", results);
    }
  };
}

filterUsers((user) => user.age > 25);

Example: Promise wrapper for easier async/await

// Helper to promisify IndexedDB
function promisifyRequest(request) {
  return new Promise((resolve, reject) => {
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error);
  });
}

// Async wrapper class
class Database {
  constructor(dbName, version) {
    this.dbName = dbName;
    this.version = version;
  }
  
  async open(upgradeCallback) {
    const request = indexedDB.open(this.dbName, this.version);
    request.onupgradeneeded = upgradeCallback;
    this.db = await promisifyRequest(request);
    return this.db;
  }
  
  async add(storeName, data) {
    const tx = this.db.transaction([storeName], "readwrite");
    const store = tx.objectStore(storeName);
    return promisifyRequest(store.add(data));
  }
  
  async get(storeName, key) {
    const tx = this.db.transaction([storeName], "readonly");
    const store = tx.objectStore(storeName);
    return promisifyRequest(store.get(key));
  }
  
  async getAll(storeName) {
    const tx = this.db.transaction([storeName], "readonly");
    const store = tx.objectStore(storeName);
    return promisifyRequest(store.getAll());
  }
  
  async delete(storeName, key) {
    const tx = this.db.transaction([storeName], "readwrite");
    const store = tx.objectStore(storeName);
    return promisifyRequest(store.delete(key));
  }
}

// Usage with async/await
async function useDatabase() {
  const db = new Database("MyDB", 1);
  
  await db.open((event) => {
    const database = event.target.result;
    database.createObjectStore("users", { "keyPath": "id", "autoIncrement": true });
  });
  
  const id = await db.add("users", { "name": "John", "email": "john@example.com" });
  console.log("Added user:", id);
  
  const user = await db.get("users", id);
  console.log("User:", user);
  
  const allUsers = await db.getAll("users");
  console.log("All users:", allUsers);
}
Note: IndexedDB is asynchronous and event-based. Schema changes (createObjectStore, createIndex) only allowed in upgradeneeded event. Use put() for upsert (insert or update), add() fails if key exists. IndexedDB can store large amounts of data (hundreds of MB to GB depending on browser).
Warning: All IndexedDB operations are transactional. Transaction auto-commits when all requests complete. Don't perform async operations (fetch, setTimeout) inside transaction handlers - transaction will close. Use indexes for queries - full table scans with cursors are slow on large datasets.

3. Cache API for Service Worker Integration

Method Syntax Description Browser Support
caches.open caches.open(cacheName) Opens named cache. Creates if doesn't exist. Returns Promise<Cache>. Modern Browsers
caches.match caches.match(request, options) Searches all caches for matching request. Returns Promise<Response> or undefined. Modern Browsers
caches.has caches.has(cacheName) Checks if named cache exists. Returns Promise<boolean>. Modern Browsers
caches.delete caches.delete(cacheName) Deletes named cache. Returns Promise<boolean> indicating if cache existed. Modern Browsers
caches.keys caches.keys() Returns Promise with array of cache names. Modern Browsers
Cache Method Description Use Case
add Fetches URL and stores response. Single request. Cache single resource
addAll Fetches array of URLs and stores responses. Atomic - fails if any fetch fails. Cache multiple resources
put Stores request-response pair directly. Full control over what's cached. Cache custom responses
match Searches cache for matching request. Returns Promise<Response> or undefined. Retrieve cached response
matchAll Returns all matching responses. Optional request parameter for filtering. Get multiple cached items
delete Removes cached request-response pair. Returns Promise<boolean>. Invalidate cache entry
keys Returns all request keys in cache. Optional request parameter for filtering. List cached URLs

Example: Basic Cache API usage

// Open cache and add resources
async function cacheResources() {
  const cache = await caches.open("my-cache-v1");
  
  // Add single resource
  await cache.add("/api/data");
  
  // Add multiple resources (atomic operation)
  await cache.addAll([
    "/",
    "/styles.css",
    "/script.js",
    "/logo.png"
  ]);
  
  console.log("Resources cached");
}

// Check cache before fetching
async function fetchWithCache(url) {
  // Try cache first
  const cachedResponse = await caches.match(url);
  if (cachedResponse) {
    console.log("Cache hit:", url);
    return cachedResponse;
  }
  
  // Cache miss - fetch from network
  console.log("Cache miss:", url);
  const response = await fetch(url);
  
  // Store in cache for next time
  const cache = await caches.open("my-cache-v1");
  cache.put(url, response.clone());
  
  return response;
}

// Usage
const response = await fetchWithCache("/api/users");
const data = await response.json();

Example: Cache strategies

// Cache First (good for static assets)
async function cacheFirst(request) {
  const cached = await caches.match(request);
  if (cached) return cached;
  
  const response = await fetch(request);
  const cache = await caches.open("static-v1");
  cache.put(request, response.clone());
  return response;
}

// Network First (good for dynamic data)
async function networkFirst(request) {
  try {
    const response = await fetch(request);
    const cache = await caches.open("dynamic-v1");
    cache.put(request, response.clone());
    return response;
  } catch (error) {
    const cached = await caches.match(request);
    if (cached) return cached;
    throw error;
  }
}

// Stale While Revalidate (best of both)
async function staleWhileRevalidate(request) {
  const cached = await caches.match(request);
  
  const fetchPromise = fetch(request).then((response) => {
    const cache = caches.open("swr-v1");
    cache.then((c) => c.put(request, response.clone()));
    return response;
  });
  
  return cached || fetchPromise;
}

// Cache with expiration
async function cacheWithExpiry(request, cacheName, maxAge) {
  const cache = await caches.open(cacheName);
  const cached = await cache.match(request);
  
  if (cached) {
    const cachedDate = new Date(cached.headers.get("date"));
    const age = Date.now() - cachedDate.getTime();
    
    if (age < maxAge) {
      return cached; // Still fresh
    }
  }
  
  // Expired or missing - fetch fresh
  const response = await fetch(request);
  cache.put(request, response.clone());
  return response;
}

Example: Cache management

// List all caches
async function listCaches() {
  const cacheNames = await caches.keys();
  console.log("Caches:", cacheNames);
  return cacheNames;
}

// Delete old cache versions
async function deleteOldCaches(currentVersion) {
  const cacheNames = await caches.keys();
  const deletePromises = cacheNames
    .filter((name) => name !== currentVersion)
    .map((name) => caches.delete(name));
  
  await Promise.all(deletePromises);
  console.log("Old caches deleted");
}

// Usage in Service Worker activate event
self.addEventListener("activate", (event) => {
  event.waitUntil(
    deleteOldCaches("my-cache-v2")
  );
});

// List cached URLs
async function listCachedUrls(cacheName) {
  const cache = await caches.open(cacheName);
  const requests = await cache.keys();
  const urls = requests.map((req) => req.url);
  console.log("Cached URLs:", urls);
  return urls;
}

// Remove specific cached item
async function removeCachedItem(url) {
  const cache = await caches.open("my-cache-v1");
  const deleted = await cache.delete(url);
  console.log(`Cache entry deleted: ${deleted}`);
}

// Clear all caches
async function clearAllCaches() {
  const cacheNames = await caches.keys();
  await Promise.all(
    cacheNames.map((name) => caches.delete(name))
  );
  console.log("All caches cleared");
}
Note: Cache API stores Request-Response pairs, not just data. Primarily used with Service Workers for offline functionality. Responses must be clone()d before caching because Response body can only be read once. Cache is persistent and separate from HTTP cache.
Property Description Format
document.cookie Gets all cookies as semicolon-separated string. Setting adds/updates single cookie. "name=value; name2=value2"
Cookie Attribute Description Example
expires Expiration date (GMT format). Cookie deleted after this date. expires=Wed, 01 Jan 2025 00:00:00 GMT
max-age Lifetime in seconds. Overrides expires. Negative value deletes cookie. max-age=3600
path URL path where cookie is accessible. Defaults to current path. path=/
domain Domain where cookie is accessible. Includes subdomains if specified. domain=.example.com
secure Cookie only sent over HTTPS. Essential for sensitive data. secure
httpOnly Cookie inaccessible to JavaScript (document.cookie). Only server-side. Set by server. httpOnly
samesite CSRF protection. Values: Strict, Lax, None (requires Secure). samesite=Strict
// Set cookie
function setCookie(name, value, days = 7, options = {}) {
  let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
  
  // Expiration
  if (days) {
    const date = new Date();
    date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
    cookie += `; expires=${date.toUTCString()}`;
  }
  
  // Path (default to root)
  cookie += `; path=${options.path || "/"}`;
  
  // Domain
  if (options.domain) {
    cookie += `; domain=${options.domain}`;
  }
  
  // Secure
  if (options.secure) {
    cookie += "; secure";
  }
  
  // SameSite
  if (options.sameSite) {
    cookie += `; samesite=${options.sameSite}`;
  }
  
  document.cookie = cookie;
}

// Get cookie
function getCookie(name) {
  const cookies = document.cookie.split("; ");
  for (let cookie of cookies) {
    const [cookieName, cookieValue] = cookie.split("=");
    if (decodeURIComponent(cookieName) === name) {
      return decodeURIComponent(cookieValue);
    }
  }
  return null;
}

// Delete cookie
function deleteCookie(name, options = {}) {
  setCookie(name, "", -1, options);
}

// Get all cookies as object
function getAllCookies() {
  const cookies = {};
  document.cookie.split("; ").forEach((cookie) => {
    const [name, value] = cookie.split("=");
    cookies[decodeURIComponent(name)] = decodeURIComponent(value);
  });
  return cookies;
}

// Usage
setCookie("username", "john_doe", 30, {
  "path": "/",
  "secure": true,
  "sameSite": "Strict"
});

const username = getCookie("username");
console.log(username); // "john_doe"

deleteCookie("username");
Note: Cookies have 4KB size limit per cookie. Always use encodeURIComponent() for names and values. HttpOnly cookies can't be accessed via JavaScript - only set by server. Use SameSite=Strict or Lax for CSRF protection.
Warning: Cookies are sent with every HTTP request to the domain - impacts performance. Don't store sensitive data in client-accessible cookies. Secure flag is mandatory for SameSite=None. Deleting cookie requires matching path and domain of original cookie.

5. Origin Private File System API

Method Syntax Description Browser Support
getDirectory NEW navigator.storage.getDirectory() Returns Promise<FileSystemDirectoryHandle> for origin's private file system root. Modern Browsers
getFileHandle dir.getFileHandle(name, options) Gets file handle. Options: create: true to create if missing. Modern Browsers
getDirectoryHandle dir.getDirectoryHandle(name, options) Gets subdirectory handle. Options: create: true to create if missing. Modern Browsers
removeEntry dir.removeEntry(name, options) Deletes file or directory. Options: recursive: true for directories. Modern Browsers
getFile fileHandle.getFile() Returns Promise<File> object with file data. Modern Browsers
createWritable fileHandle.createWritable() Returns Promise<FileSystemWritableFileStream> for writing. Modern Browsers

Example: Origin Private File System usage

// Get root directory
const root = await navigator.storage.getDirectory();

// Create/get file
const fileHandle = await root.getFileHandle("data.txt", { "create": true });

// Write to file
const writable = await fileHandle.createWritable();
await writable.write("Hello, File System!");
await writable.write({ "type": "write", "data": "\nNew line" });
await writable.close();

// Read from file
const file = await fileHandle.getFile();
const contents = await file.text();
console.log(contents);

// Create directory
const dirHandle = await root.getDirectoryHandle("documents", { "create": true });

// Create file in subdirectory
const subFileHandle = await dirHandle.getFileHandle("note.txt", { "create": true });

// List directory contents
for await (const entry of root.values()) {
  console.log(entry.kind, entry.name); // "file" or "directory"
}

// Delete file
await root.removeEntry("data.txt");

// Delete directory recursively
await root.removeEntry("documents", { "recursive": true });
Note: Origin Private File System is private to origin and not accessible to user. Good for app-specific data, cache, temporary files. Not for user documents - use File System Access API for that. Storage is persistent and survives browser restarts.

6. Storage Quota and Usage Estimation

Method Syntax Description Browser Support
estimate navigator.storage.estimate() Returns Promise with quota and usage info. Properties: quota, usage. Modern Browsers
persist navigator.storage.persist() Requests persistent storage (won't be cleared under pressure). Returns Promise<boolean>. Modern Browsers
persisted navigator.storage.persisted() Checks if storage is persistent. Returns Promise<boolean>. Modern Browsers

Example: Checking storage quota

// Check storage quota and usage
async function checkStorageQuota() {
  if (navigator.storage && navigator.storage.estimate) {
    const estimate = await navigator.storage.estimate();
    const usage = estimate.usage; // Bytes used
    const quota = estimate.quota; // Bytes available
    const percentUsed = (usage / quota * 100).toFixed(2);
    
    console.log(`Storage: ${formatBytes(usage)} / ${formatBytes(quota)}`);
    console.log(`${percentUsed}% used`);
    
    if (estimate.usageDetails) {
      console.log("Breakdown:", estimate.usageDetails);
      // { indexedDB: 1234, caches: 5678, ... }
    }
    
    return { usage, quota, percentUsed };
  }
}

function formatBytes(bytes) {
  if (bytes === 0) return "0 Bytes";
  const k = 1024;
  const sizes = ["Bytes", "KB", "MB", "GB"];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return Math.round(bytes / Math.pow(k, i) * 100) / 100 + " " + sizes[i];
}

// Request persistent storage
async function requestPersistentStorage() {
  if (navigator.storage && navigator.storage.persist) {
    const isPersisted = await navigator.storage.persist();
    console.log(`Persistent storage: ${isPersisted}`);
    return isPersisted;
  }
  return false;
}

// Check if storage is persistent
async function checkPersistence() {
  if (navigator.storage && navigator.storage.persisted) {
    const isPersisted = await navigator.storage.persisted();
    console.log(`Storage persisted: ${isPersisted}`);
    return isPersisted;
  }
  return false;
}

// Monitor storage before operations
async function monitorStorage() {
  const before = await navigator.storage.estimate();
  
  // Perform storage operations
  await performHeavyStorageOperation();
  
  const after = await navigator.storage.estimate();
  const increased = after.usage - before.usage;
  console.log(`Storage increased by: ${formatBytes(increased)}`);
}
Note: Quota varies by browser and available disk space (typically 10-50% of available disk). persist() requires user interaction or installed PWA. Persistent storage won't be cleared under storage pressure. Usage includes IndexedDB, Cache API, and File System API data.

Storage API Best Practices

  • Use localStorage for small, simple data (settings, preferences) - synchronous and limited to 5-10MB
  • Use IndexedDB for large structured data (offline databases, cached content) - asynchronous and scalable
  • Use Cache API with Service Workers for offline-first applications and resource caching
  • Always wrap storage operations in try/catch - quota exceeded and private mode can cause errors
  • Use JSON.stringify/parse for complex objects in localStorage
  • Set appropriate cookie attributes: Secure, HttpOnly, SameSite
  • Request persistent storage for critical data with navigator.storage.persist()
  • Monitor quota usage with navigator.storage.estimate() before large operations
  • Never store sensitive data (passwords, tokens) in client-side storage - vulnerable to XSS
  • Consider data expiration strategies for cached content to prevent stale data