Web APIs Cheatsheet

Storage (localStorage, Cookies, IndexedDB)

Use this Web APIs reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

localStorage and sessionStorage

Both implement the Storage interface. localStorage persists across sessions; sessionStorage is scoped to the browser tab.

// Set
localStorage.setItem('key', 'value');
localStorage.setItem('obj', JSON.stringify({ a: 1 }));

// Get
localStorage.getItem('key');          // 'value' or null
JSON.parse(localStorage.getItem('obj')); // { a: 1 }

// Remove
localStorage.removeItem('key');
localStorage.clear();                 // remove all

// Enumerate
localStorage.length;                  // number of items
localStorage.key(0);                  // key at index
for (let i = 0; i < localStorage.length; i++) {
  const k = localStorage.key(i);
  console.log(k, localStorage.getItem(k));
}
// or
Object.entries(localStorage);         // all [key, value] pairs

Storage API Quick Reference

Method / PropertyDescription
.setItem(key, value)Store string value
.getItem(key)Retrieve string or null
.removeItem(key)Delete entry
.clear()Delete all entries
.key(index)Get key at numeric index
.lengthNumber of stored items

Storage Event

// Fires on OTHER tabs in the same origin (not the writing tab)
window.addEventListener('storage', e => {
  e.key;          // changed key (null if clear() called)
  e.oldValue;     // previous value string
  e.newValue;     // new value string (null if removed)
  e.url;          // URL of the page that changed storage
  e.storageArea;  // localStorage or sessionStorage reference
});

Gotchas

  • Only strings are stored. Always JSON.stringify / JSON.parse for objects.
  • localStorage is synchronous and blocks the main thread on large reads/writes — prefer IndexedDB for bulk data.
  • Quota is ~5–10 MB per origin. Exceeds throw DOMException: QuotaExceededError.
  • Not available in file:// origins or private browsing in some browsers.
  • sessionStorage is NOT shared between tabs, even on the same origin.

Cookies

Reading and Writing

// Read all cookies as a single string
document.cookie; // 'name=Alice; theme=dark'

// Parse cookies
const cookies = Object.fromEntries(
  document.cookie.split('; ').map(c => c.split('=').map(decodeURIComponent))
);
cookies.name; // 'Alice'

// Set a cookie (one at a time)
document.cookie = 'theme=dark';
document.cookie = `token=${encodeURIComponent(val)}; path=/; max-age=86400; SameSite=Lax; Secure`;

// Delete (set max-age=0 or expires in past)
document.cookie = 'theme=; max-age=0; path=/';

Cookie Attributes

AttributeDescription
path=/Which paths the cookie is sent on (default: current dir)
domain=example.comDomain scope (leading dot covers subdomains)
max-age=NLifetime in seconds; overrides expires
expires=DATEExpiry date string (UTC)
SecureHTTPS only
HttpOnlyNot accessible from JS (set by server)
SameSite=StrictNever sent cross-site
SameSite=LaxSent on top-level navigations (default in modern browsers)
SameSite=None; SecureAlways sent cross-site (requires Secure)

Cookie Store API (async, modern)

// Read
const cookie = await cookieStore.get('token');
cookie?.value;

const all = await cookieStore.getAll();

// Write
await cookieStore.set({ name: 'theme', value: 'dark', maxAge: 86400 });

// Delete
await cookieStore.delete('theme');

// Change events (ServiceWorker and page context)
cookieStore.addEventListener('change', e => {
  e.changed; // array of changed cookies
  e.deleted; // array of deleted cookies
});

IndexedDB

Asynchronous, transactional, key-value/object-store DB. Quota is typically 50%+ of disk. Supports complex queries via indexes.

Open and Upgrade

const request = indexedDB.open('MyDB', 2 /* version */);

request.onupgradeneeded = e => {
  const db = e.target.result;
  const oldVersion = e.oldVersion;

  if (oldVersion < 1) {
    const store = db.createObjectStore('users', { keyPath: 'id', autoIncrement: true });
    store.createIndex('by-email', 'email', { unique: true });
    store.createIndex('by-name', 'name', { unique: false });
  }
  if (oldVersion < 2) {
    db.createObjectStore('settings', { keyPath: 'key' });
  }
};

request.onsuccess = e => {
  const db = e.target.result;
  db.onversionchange = () => { db.close(); location.reload(); };
};
request.onerror = e => console.error(e.target.error);

CRUD Operations

// Helper: wrap IDB request in a Promise
function idbRequest(req) {
  return new Promise((res, rej) => {
    req.onsuccess = () => res(req.result);
    req.onerror = () => rej(req.error);
  });
}

// Add / Put
const tx = db.transaction('users', 'readwrite');
const store = tx.objectStore('users');
const id = await idbRequest(store.add({ name: 'Alice', email: 'a@a.com' }));
await idbRequest(store.put({ id, name: 'Alice B', email: 'a@a.com' })); // upsert
await tx.done; // only available in idb library; else use tx.oncomplete

// Get
const tx2 = db.transaction('users', 'readonly');
const user = await idbRequest(tx2.objectStore('users').get(id));

// Get by index
const byEmail = await idbRequest(
  tx2.objectStore('users').index('by-email').get('a@a.com')
);

// Delete
const tx3 = db.transaction('users', 'readwrite');
await idbRequest(tx3.objectStore('users').delete(id));

// Clear store
await idbRequest(tx3.objectStore('users').clear());

Cursors and Range Queries

const tx = db.transaction('users', 'readonly');
const store = tx.objectStore('users');
const index = store.index('by-name');

// IDBKeyRange
IDBKeyRange.only('Alice');
IDBKeyRange.lowerBound('A', false);   // false = inclusive
IDBKeyRange.upperBound('Z', true);    // true = exclusive
IDBKeyRange.bound('A', 'M', false, true);

// Cursor
const req = index.openCursor(IDBKeyRange.bound('A', 'M'));
req.onsuccess = e => {
  const cursor = e.target.result;
  if (!cursor) return; // done
  console.log(cursor.key, cursor.value);
  cursor.continue();   // next
  // cursor.advance(2) — skip 2
  // cursor.update({...cursor.value, active: true})
  // cursor.delete()
};

// getAll (no cursor overhead)
const all = await idbRequest(store.getAll());
const range = await idbRequest(store.getAll(IDBKeyRange.lowerBound(10), 50 /* limit */));
const keys = await idbRequest(store.getAllKeys());
const count = await idbRequest(store.count());

Transactions

const tx = db.transaction(['users', 'settings'], 'readwrite');
tx.oncomplete = () => console.log('committed');
tx.onerror = e => console.error(e.target.error);
tx.onabort = () => console.log('aborted');
tx.abort(); // manually roll back

// Transactions auto-commit when no more requests are queued
// Do NOT await non-IDB promises inside a transaction — it will close

idb Library (recommended wrapper)

import { openDB } from 'idb'; // npm install idb

const db = await openDB('MyDB', 1, {
  upgrade(db) {
    db.createObjectStore('notes', { keyPath: 'id', autoIncrement: true });
  },
});

await db.add('notes', { text: 'Hello', created: Date.now() });
const note = await db.get('notes', 1);
const all = await db.getAll('notes');
await db.put('notes', { id: 1, text: 'Updated' });
await db.delete('notes', 1);

// Transaction shorthand
const tx = db.transaction('notes', 'readwrite');
await tx.store.add({ text: 'New' });
await tx.done;

Cache API (Service Worker)

// Open / create cache
const cache = await caches.open('v1');

// Add requests
await cache.add('/index.html');
await cache.addAll(['/style.css', '/app.js']);

// Match
const res = await cache.match('/index.html');

// Put (manual)
const freshRes = await fetch('/data.json');
await cache.put('/data.json', freshRes.clone());

// Delete
await cache.delete('/old.js');

// List all cache names
const names = await caches.keys();

// Delete old caches
await caches.delete('v0');

// Check if a URL is cached
const hit = await caches.match('/style.css');

Storage Quota API

const { usage, quota } = await navigator.storage.estimate();
console.log(`${(usage / quota * 100).toFixed(1)}% used`);

// Request persistent storage (won't be evicted automatically)
const granted = await navigator.storage.persist();
const isPersisted = await navigator.storage.persisted();