Browser Storage for Extension Developers: localStorage vs. browser.storage.local
Source: Dev.to
The Short Answer
Use browser.storage.local. Not localStorage. Not sessionStorage. Not indexedDB (unless you have very specific needs).
Why Not localStorage?
It doesn’t work in background scripts
// In a background script — THIS WILL FAIL
localStorage.setItem('key', 'value'); // ReferenceError: window is not defined
Background scripts run in a service worker (MV3) or background page (MV2) context, neither of which provides a window object.
It doesn’t sync across devices
Even when used in a content script or popup, localStorage is confined to the local device. To sync data across Firefox installations, use browser.storage.sync.
Private browsing isolation
In private windows, localStorage is isolated and cleared when the window closes. browser.storage.local persists regardless of private browsing mode.
browser.storage.local API
The API is promise‑based and works consistently across extension contexts.
// Save data
await browser.storage.local.set({
theme: 'dark',
city: 'San Francisco',
clocks: [
{ zone: 'America/New_York', label: 'NYC' },
{ zone: 'Europe/London', label: 'London' }
]
});
// Read specific keys
const prefs = await browser.storage.local.get(['theme', 'city', 'clocks']);
console.log(prefs.theme); // 'dark'
// Read all data
const allPrefs = await browser.storage.local.get(null);
// Remove a key
await browser.storage.local.remove('city');
// Clear everything
await browser.storage.local.clear();
browser.storage.sync
Use this for data that should be synchronized across the user’s Firefox installations.
// Save to sync storage
await browser.storage.sync.set({ theme: 'dark' });
// Read from sync storage
const prefs = await browser.storage.sync.get('theme');
Limitations
- Maximum total data: 100 KB
- Maximum per key: 8 KB
- Maximum items: 512
- Firefox Sync must be enabled
For the Weather & Clock Dashboard extension, storage.local was chosen because the settings are device‑specific and do not require Sync.
Listening for Changes
A useful pattern for reactive UIs:
browser.storage.onChanged.addListener((changes, areaName) => {
if (areaName === 'local') {
if (changes.theme) {
applyTheme(changes.theme.newValue);
}
if (changes.city) {
fetchWeather(changes.city.newValue);
}
}
});
This allows different parts of the extension to react to storage updates without direct coupling.
Storage Size Limits
browser.storage.local has no default quota. Requesting the unlimitedStorage permission removes any browser‑imposed limits:
// manifest.json
{
"permissions": ["storage", "unlimitedStorage"]
}
For most extensions the default quota is sufficient; the permission is only needed when storing large amounts of data.
Pattern: Settings with Defaults
const DEFAULTS = {
theme: 'light',
city: 'New York',
clocks: [{ zone: 'America/New_York', label: 'Local' }]
};
async function getSettings() {
const stored = await browser.storage.local.get(Object.keys(DEFAULTS));
return { ...DEFAULTS, ...stored }; // stored values override defaults
}
This approach lets you add new settings with sensible defaults without breaking existing users.
Practical Example
The Weather & Clock Dashboard extension stores all user preferences using the pattern above:
// Load settings on new tab open
const settings = await browser.storage.local.get(null);
const city = settings.city || 'New York';
const clocks = settings.clocks || DEFAULT_CLOCKS;
const theme = settings.theme || 'light';
applyTheme(theme);
renderClocks(clocks);
fetchWeather(city);
Simple, reliable, and works everywhere the extension runs.
Source code: github.com/oren-sys/weather-clock-dashboard