Browser Storage Explained: Cookies, IndexedDB, Cache Storage, and OPFS

Updated on
9 min read

Browser storage lets a web application keep selected data on a user’s device between requests or visits. The right choice depends on whether data must be sent to a server, how it is structured, how much may be stored, and whether it must survive an offline session. This guide compares cookies, Web Storage, IndexedDB, Cache Storage, and the Origin Private File System (OPFS), including the limits and security responsibilities they share.

What Is Browser Storage?

Browser storage is the family of browser-managed mechanisms through which websites keep data locally. It is not one database: some APIs hold small strings, some store structured records, and others cache HTTP responses or files.

Cookies are attached to matching HTTP requests, so they can carry small pieces of state between a browser and a server. Most other browser storage stays on the client and is accessed by JavaScript, a worker, or a service worker. The Web Storage API offers localStorage and sessionStorage; IndexedDB stores structured records asynchronously.

These mechanisms are scoped by browser security boundaries. A site generally cannot read another origin’s storage, and browsers may further partition storage used in third-party contexts. Local storage is useful for a cache, draft, or preference, but it is not a replacement for a server database or a backup.

The Problem Browser Storage Solves

Without client-side persistence, an application must fetch every view from a server and loses local work when the page reloads or the network drops. Storage can reduce repeat downloads, preserve drafts, keep a user interface responsive, and make selected app features available offline.

Local storage also creates constraints. A browser can restrict or remove site data, devices have limited disk space, and data on one device does not automatically synchronize with another. Applications must decide which data is disposable cache, which user changes need synchronization, and how to recover if local data is unavailable.

How Browser Storage Works

At a high level, a page or worker calls a storage API. The browser enforces origin and privacy boundaries, applies a quota and retention policy, and may persist the data on disk. A service worker can additionally route network requests through Cache Storage. When an application needs shared or durable records across devices, it synchronizes selected changes with a server.

Application UI or worker → storage API → browser-managed origin storage → optional synchronization with an application server

Each API has its own data model, but storage is managed as part of the browser’s site data. Quotas are implementation-dependent; there is no dependable universal megabyte limit for IndexedDB, Cache Storage, or OPFS. navigator.storage.estimate() reports approximate usage and quota, not a guaranteed allocation. Writes can fail with QuotaExceededError, and best-effort data can be evicted under storage pressure or cleared by the user. See MDN’s guide to browser storage quotas and eviction.

Storage is commonly associated with an origin (scheme, host, and port), but privacy protections can partition third-party state by the top-level site. Do not assume that an embedded widget can reuse its storage across every site that embeds it. Browser rules differ; MDN describes state partitioning and the boundaries it creates.

Storage Components and Variants

Mechanism Data model and scope Good fit Main trade-offs
Cookies Small values sent with matching HTTP requests; controlled by cookie attributes Server-managed sessions and request state Request overhead; configure Secure, HttpOnly, and SameSite where appropriate
localStorage Synchronous string key/value data, generally shared by same-origin documents Small preferences that should persist across visits Blocks JavaScript while reading or writing; can be unavailable or exceed quota
sessionStorage Synchronous string key/value data scoped to an origin and page session Temporary per-tab state Not a durable record store; synchronous access
IndexedDB Asynchronous, transactional database of structured-cloneable values and indexes Records, drafts, offline data, and larger structured datasets More involved schema and transaction lifecycle; subject to quota and eviction
Cache Storage Named caches of Request/Response pairs Explicit app-managed caching of assets and network responses Does not update or expire entries automatically; the app must version and clean caches
OPFS Origin-private files and directories Large files, binary data, or storage used by in-browser tools such as database engines Not a user-visible file picker; browser quota and origin-data deletion still apply

Cookies are primarily a server communication mechanism, not a general-purpose database. localStorage and sessionStorage are convenient for small values, but their synchronous API makes them a poor fit for large or frequently accessed data. IndexedDB is designed for structured records and asynchronous operations.

Cache Storage is often used by a service worker, but the API can also be accessed by pages and other workers. The application is responsible for cache updates and cleanup; the Cache API documentation notes that cached entries do not automatically expire or follow HTTP cache headers. OPFS gives an origin private file-like storage, including synchronous access handles in workers; it is distinct from the user-visible file access flows in the OPFS guide.

Real-World Use Cases

  • Preferences: Store a theme or compact-view setting in Web Storage. Keep the value small and tolerate it being cleared.
  • Offline documents and drafts: Use IndexedDB for structured records and pending edits. Design an explicit synchronization and conflict-resolution policy if the same records can change on multiple devices.
  • Offline site shell: Use a service worker and Cache Storage for selected app assets or request/response pairs. Keep cache versioning and removal rules under application control.
  • Large local files: Consider OPFS for browser-based editors, media tools, or databases that need private file-like storage. It is not a way to write arbitrary files into a user’s visible folders without permission.
  • Server sessions: Use appropriately scoped cookies when the server needs session state on requests. Do not move authentication secrets into script-readable storage simply for convenience.

For an end-to-end design that combines local records, caching, and synchronization, see the site’s offline-first application architecture guide.

Practical Considerations

Choose by data and failure model

Use Web Storage only for small, simple strings. Use IndexedDB when records need indexes, transactions, blobs, or asynchronous access. Use Cache Storage for resources represented as HTTP requests and responses, not as a substitute for a record database. Use OPFS when a file-oriented API is a better fit for large or binary data. Keep server-owned data on the server and define whether each local copy is authoritative, a queued change, or disposable cache.

Handle storage errors and estimate usage

Even a small preference write can fail if storage is blocked or full. Handle expected browser exceptions explicitly rather than assuming persistence succeeded:

function saveTheme(theme) {
  try {
    window.localStorage.setItem('theme', theme);
    return true;
  } catch (error) {
    if (
      error instanceof DOMException &&
      ['QuotaExceededError', 'SecurityError'].includes(error.name)
    ) {
      console.warn('The theme preference could not be saved.', error);
      return false;
    }
    throw error;
  }
}

For larger offline data, inspect the browser’s estimates and request persistent storage only when the product has a clear reason to do so. The browser decides whether persistence is granted, and it does not protect data from a user clearing site data:

if (navigator.storage?.estimate) {
  const { usage = 0, quota = 0 } = await navigator.storage.estimate();
  console.info(`Estimated site storage: ${usage} of ${quota} bytes`);
}

if (navigator.storage?.persist && navigator.storage?.persisted) {
  let persistent = await navigator.storage.persisted();
  if (!persistent) {
    persistent = await navigator.storage.persist();
  }
  console.info(`Persistent storage granted: ${persistent}`);
}

Treat usage and quota as estimates, and still handle quota errors on each write. Avoid hard-coding a browser-specific size threshold.

Store structured records in IndexedDB

This small example creates a store during a schema upgrade and commits a note in a read-write transaction. Production apps should also plan version upgrades and handle blocked upgrades when older tabs still hold a connection.

function openNotesDb() {
  if (!('indexedDB' in window)) {
    throw new Error('IndexedDB is not available in this browser.');
  }

  return new Promise((resolve, reject) => {
    const request = window.indexedDB.open('notes-app', 1);

    request.onupgradeneeded = () => {
      request.result.createObjectStore('notes', { keyPath: 'id' });
    };
    request.onerror = () => {
      reject(request.error ?? new Error('Could not open the notes database.'));
    };
    request.onsuccess = () => {
      const db = request.result;
      db.onversionchange = () => db.close();
      resolve(db);
    };
  });
}

async function saveNote(note) {
  const db = await openNotesDb();
  try {
    await new Promise((resolve, reject) => {
      const transaction = db.transaction('notes', 'readwrite');
      transaction.objectStore('notes').put(note);
      transaction.oncomplete = resolve;
      transaction.onerror = () => {
        reject(transaction.error ?? new Error('Could not save the note.'));
      };
      transaction.onabort = () => {
        reject(transaction.error ?? new Error('The note transaction was aborted.'));
      };
    });
  } finally {
    db.close();
  }
}

await saveNote({ id: crypto.randomUUID(), text: 'Draft', updatedAt: Date.now() });

Protect users and plan for deletion

Any data readable by same-origin script can be exposed if an attacker executes JavaScript in the page. Keep scripts and dependencies secure, validate data when reading it, and do not treat local storage as a secure vault for passwords, payment data, or long-lived bearer tokens. HttpOnly cookies are not accessible to page JavaScript, though cookie-based authentication still needs appropriate Secure, SameSite, and CSRF protections.

Make stored records disposable where possible, expire stale entries, and provide a clear recovery path for irreplaceable user work. Test offline behavior, storage denial, quota errors, browser restart, and cleared site data. Persistent storage can reduce automatic eviction, but it is not a backup or cross-device synchronization system.

Common Misconceptions

  • “Browser storage has a fixed limit.” Web Storage has relatively small limits, but IndexedDB, Cache Storage, and OPFS quotas vary by browser, device, and available space.
  • “Saved data is permanent.” Best-effort data may be evicted, and users can clear site data. Persistence requests are browser-controlled.
  • “Cache Storage is the browser’s HTTP cache.” It is a separate, application-managed API; entries need explicit update and cleanup logic.
  • “Local storage is safe for secrets because it stays on the device.” Same-origin scripts can access it, so an XSS flaw can expose its contents.
  • “Storage is always shared with an embedded copy of the same site.” Privacy partitioning can give third-party embeds a separate storage context.
  • “Offline storage automatically syncs.” Browser APIs store local data; applications still need a network protocol, retry rules, and conflict handling to synchronize it.
TBO Editorial

About the Author

TBO Editorial writes about the latest updates about products and services related to Technology, Business, Finance & Lifestyle. Do get in touch if you want to share any useful article with our community.