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
-
localStorage— synchronous, 5-10MB limit, per-origin -
sessionStorage— same as localStorage but clears on session end -
IndexedDB— async, larger limits, supports complex queries -
browser.storage.local— async, up to 10MB by default, extension-isolated -
browser.storage.sync— synced across devices, 100KB limit, quota per item -
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();
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');
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'
]);
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 });
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()
}));
}
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);
}
}
});
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');
}
}
Part of a series on building Firefox browser extensions.
Install the extension: Weather & Clock Dashboard on AMO
Top comments (0)