DEV Community

Weather Clock Dash
Weather Clock Dash

Posted on

Browser Storage for Firefox Extensions: localStorage vs IndexedDB vs storage.local

Browser Storage for Firefox Extensions: localStorage vs IndexedDB vs storage.local

One of the first decisions when building a browser extension is where to store data. There are more options than you might expect, each with distinct tradeoffs.

The Options

  1. localStorage — synchronous, 5-10MB limit, per-origin
  2. sessionStorage — same as localStorage but clears on session end
  3. IndexedDB — async, larger limits, supports complex queries
  4. browser.storage.local — async, up to 10MB by default, extension-isolated
  5. browser.storage.sync — synced across devices, 100KB limit, quota per item
  6. browser.storage.session — in-memory, cleared on extension restart (MV3 only)

When to Use What

browser.storage.local — The Default Choice for Extensions

// Store data
await browser.storage.local.set({ 
  weatherData: { temp: 72, city: 'San Francisco' },
  lastUpdated: Date.now()
});

// Read data
const { weatherData, lastUpdated } = await browser.storage.local.get([
  'weatherData', 
  'lastUpdated'
]);

// Delete specific key
await browser.storage.local.remove('weatherData');

// Clear everything
await browser.storage.local.clear();
Enter fullscreen mode Exit fullscreen mode

Use when: You need to persist data between sessions, and it's extension-specific (not shared with web pages). The API is clean and consistent across browsers.

localStorage — Quick and Dirty

// Synchronous — no await needed
localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme');
Enter fullscreen mode Exit fullscreen mode

Use when: Simple key-value data in your extension pages (popup, options, newtab). Not accessible from content scripts in a web page's origin.

Don't use when: You need to read data in background scripts reliably — background pages and your UI pages share localStorage, but service workers (MV3) do NOT have access to localStorage.

browser.storage.sync — Cross-Device Preferences

// User preferences that should follow them across devices
await browser.storage.sync.set({
  searchEngine: 'duckduckgo',
  temperatureUnit: 'celsius',
  timeFormat: '24h'
});

const prefs = await browser.storage.sync.get([
  'searchEngine',
  'temperatureUnit',
  'timeFormat'
]);
Enter fullscreen mode Exit fullscreen mode

Limits:

  • Max 100KB total
  • Max 8KB per item
  • Max 512 items
  • 1800 write operations per hour

Use when: Small user preferences that should persist across Firefox installations. Does NOT work if sync is disabled.

IndexedDB — Large or Complex Data

const db = await new Promise((resolve, reject) => {
  const req = indexedDB.open('weather-extension', 1);
  req.onupgradeneeded = (e) => {
    const db = e.target.result;
    db.createObjectStore('forecasts', { keyPath: 'date' });
  };
  req.onsuccess = (e) => resolve(e.target.result);
  req.onerror = (e) => reject(e.target.error);
});

// Store forecast data
const tx = db.transaction('forecasts', 'readwrite');
tx.objectStore('forecasts').put({ date: '2024-01-15', data: forecastData });
Enter fullscreen mode Exit fullscreen mode

Use when: You need to store large amounts of data (historical weather, search history), or need to query/filter data.

The Pattern for Weather & Clock Dashboard

For the Weather & Clock Dashboard extension, I use:

// Persistent user preferences → browser.storage.local
const PREFS_DEFAULTS = {
  city: '',
  temperatureUnit: 'fahrenheit',
  timeFormat: '12h',
  theme: 'system',
  worldClocks: [
    { label: 'London', timezone: 'Europe/London' },
    { label: 'Tokyo', timezone: 'Asia/Tokyo' }
  ]
};

async function getPrefs() {
  const stored = await browser.storage.local.get(Object.keys(PREFS_DEFAULTS));
  return { ...PREFS_DEFAULTS, ...stored };
}

async function setPrefs(updates) {
  await browser.storage.local.set(updates);
}

// Weather cache → localStorage (same origin as newtab.html)
function getCachedWeather() {
  const raw = localStorage.getItem('weatherCache');
  if (!raw) return null;
  const { data, timestamp } = JSON.parse(raw);
  const age = Date.now() - timestamp;
  return age < 3600000 ? { data, age } : null; // 1 hour TTL
}

function setCachedWeather(data) {
  localStorage.setItem('weatherCache', JSON.stringify({
    data,
    timestamp: Date.now()
  }));
}
Enter fullscreen mode Exit fullscreen mode

Listening for Storage Changes

// React to storage changes from any part of the extension
browser.storage.onChanged.addListener((changes, area) => {
  if (area === 'local') {
    if (changes.theme) {
      applyTheme(changes.theme.newValue);
    }
    if (changes.city) {
      refreshWeather(changes.city.newValue);
    }
  }
});
Enter fullscreen mode Exit fullscreen mode

This is how the options page and newtab page can stay in sync without direct messaging.

Checking Quota Usage

async function checkStorageQuota() {
  const usage = await browser.storage.local.getBytesInUse(null);
  console.log(`Storage used: ${(usage / 1024).toFixed(1)} KB`);

  // browser.storage.local.QUOTA_BYTES = 10485760 (10MB)
  const pct = (usage / browser.storage.local.QUOTA_BYTES) * 100;
  if (pct > 80) {
    console.warn('Storage > 80% full');
  }
}
Enter fullscreen mode Exit fullscreen mode

Part of a series on building Firefox browser extensions.

Install the extension: Weather & Clock Dashboard on AMO

firefox #javascript #webdev #browserextension

Top comments (0)