← Back to Blog
Code Snippets July 2, 2026

LocalStorage with TTL and Auto-Cleanup

:globe_showing_Americas: Code Snippet - July 2, 2026

LocalStorage with TTL and Auto-Cleanup

localStorage data that never expires? This wrapper adds per-key expiration, auto-cleanup, quota awareness, and in-memory fallback.

class TTLStorage {
  constructor(prefix = 'ttl_') {
    this.prefix = prefix;
    this._avail = (() => {
      try { localStorage.setItem('t', '1'); localStorage.removeItem('t'); return true; }
      catch { return false; }
    })();
    this._mem = new Map();
  }
  set(name, value, opts = {}) {
    const ttl = opts.ttl ?? 86400;
    const d = JSON.stringify({ v: value, e: ttl === 0 ? 0 : Date.now() + ttl * 1000 });
    if (!this._avail) return this._mem.set(name, d);
    try { return localStorage.setItem(this.prefix + name, d); }
    catch (err) {
      if (err.name === 'QuotaExceededError') { this.cleanup(); try { return localStorage.setItem(this.prefix + name, d); } catch { return false; } }
      throw err;
    }
  }
  get(name) {
    const raw = this._avail ? localStorage.getItem(this.prefix + name) : this._mem.get(name);
    if (!raw) return null;
    try { const { v, e } = JSON.parse(raw); return (e === 0 || Date.now() > e) ? null : v; } catch { return null; }
  }
  cleanup() {
    let n = 0;
    if (this._avail) for (let i = 0; i < localStorage.length; i++) {
      const k = localStorage.key(i);
      if (!k || !k.startsWith(this.prefix)) continue;
      try { const { e } = JSON.parse(localStorage.getItem(k)); if (e === 0 || Date.now() > e) { localStorage.removeItem(k); n++; } }
      catch { localStorage.removeItem(k); n++; }
    }
    return n;
  }
}


const s = new TTLStorage();
s.set('token', 'abc', { ttl: 3600 });  // 1h
s.set('prefs', { theme: 'dark' });     // 24h default
s.set('sess', true, { ttl: 0 });       // expires on reload
s.cleanup(); // run on startup


:bulb: Auto-cleans, respects quotas, falls back to memory.