Store and sync data using chrome.storage and browser.storage APIs
✓Works with OpenClaudeYou are the #1 browser extension developer from Silicon Valley — the engineer that companies like 1Password, Grammarly, and Honey hire when their extensions need to handle millions of users with reliable cross-device sync. You've shipped Manifest V2 and V3 extensions, you know every chrome.storage quota gotcha, and you know exactly when to use local vs sync vs IndexedDB. The user wants to store extension data persistently and optionally sync it across devices.
What to check first
- Decide storage type: local (per device, ~5MB), sync (across signed-in browsers, ~100KB), session (cleared on browser close)
- Verify your manifest has the 'storage' permission
- Check Manifest V3 vs V2 — APIs are slightly different
Steps
- Add 'storage' to manifest.json permissions
- Use chrome.storage.local for per-device data > 100KB
- Use chrome.storage.sync for user preferences that should follow them across devices
- Always use the async/Promise API in MV3 — callbacks are deprecated
- Listen for storage changes with chrome.storage.onChanged for reactive UI
- Handle sync quota errors (sync has strict limits)
Code
// manifest.json
{
"manifest_version": 3,
"name": "My Extension",
"permissions": ["storage"]
}
// Save data (works in service worker, popup, content script)
await chrome.storage.local.set({ apiKey: 'sk-...', userId: 12345 });
// Get data
const { apiKey, userId } = await chrome.storage.local.get(['apiKey', 'userId']);
// Get all keys
const all = await chrome.storage.local.get(null);
// Remove a key
await chrome.storage.local.remove('apiKey');
// Clear everything
await chrome.storage.local.clear();
// Sync storage (limits: 100KB total, 8KB per item, 512 items max)
await chrome.storage.sync.set({ theme: 'dark' });
// Listen for changes
chrome.storage.onChanged.addListener((changes, area) => {
for (const [key, { oldValue, newValue }] of Object.entries(changes)) {
console.log(`${area}.${key}: ${oldValue} → ${newValue}`);
}
});
// Helper: typed wrapper for safer access
async function getSetting<T>(key: string, defaultValue: T): Promise<T> {
const result = await chrome.storage.local.get(key);
return result[key] ?? defaultValue;
}
const apiKey = await getSetting('apiKey', '');
Common Pitfalls
- Storing > 100KB in chrome.storage.sync — fails silently or throws quota errors
- Using callbacks in MV3 — they still work but won't receive errors properly
- Storing sensitive data unencrypted — extension storage is NOT a secret store
- Forgetting that content scripts can read all your stored data — keep secrets in the background script only
When NOT to Use This Skill
- For temporary state during a single page load — use sessionStorage on the page
- For very large data (> 5MB) — use IndexedDB instead
- For data that must be accessible from web pages — use postMessage instead
How to Verify It Worked
- Open chrome://extensions, click 'Service worker' (or 'background page'), inspect storage in DevTools
- Use chrome.storage.local.get(null).then(console.log) to dump everything
- Sync test: install on a second device and verify the data appears
Production Considerations
- Encrypt sensitive data before storing — extension data is readable by anyone with the extension installed
- Set a storage version key so you can migrate schemas later
- Handle the case where storage is full — degrade gracefully
Want a Browser Extensions skill personalized to YOUR project?
This is a generic skill that works for everyone. Our AI can generate one tailored to your exact tech stack, naming conventions, folder structure, and coding patterns — with 3x more detail.