here’s how i implement and manage settings in my chrome extension using local storage (e.g. isDark, userLanguage, etc…)
Why Proper Settings Management Matters
- Maintainability: Good structure makes your code easier to update and debug
- Performance: Efficient storage operations keep your extension fast
-
DRY: avoid repeating
chrome.storage.synceverywhere
1. chrome.storage.sync vs chrome.storage.local
Browser extensions have access to two main storage APIs (avoid session storage, bcz you need to call this function to use it from content script:
// Source - https://stackoverflow.com/questions/74424746/not-able-to-access-storage-in-content-scripts-in-chrome-extension-manifest-v3
chrome.storage.session.setAccessLevel({ accessLevel: 'TRUSTED_AND_UNTRUSTED_CONTEXTS' });
so we only have two options, sync or local:
i like chrome.storage.sync because it Automatically syncs across all browsers where the user is logged in, (like their laptop, pc, etc), you don’t need to do anything, chrome will do it
Note: Throughout this blog, I use
browser.storageinstead ofchrome.storagebecause i am using wxt framework to develop my extension , but you can easily replacebrowserwithchromekeyword. Doesn't matter.
2. Designing Your Settings Structure
keep it flat. Avoid deep nesting because it makes updates/inserts/deletes harder.
❌ Bad Structure (Nested)
// Don't do this
{
settings: {
appearance: {
theme: {
mode: "dark",
accentColor: "blue"
}
}
}
}
To update mode, you'd need to:
- Fetch the entire settings object
- Navigate through nested properties
- Update the value
- Save everything back
✅ Good Structure (Flat)
// also keep settings and user info separate (if possible)
userSettings: {
isDark: true,
isEnabled: true,
userLang: "english",
accentColor: "blue",
fontSize: "medium"
},
userInfo: {
userEmail: "example@gmail.com",
isAuthenticated: true,
creditsUsed: 54,
favCategories: ['sci-fi', 'horror', 'thriller']
}
3. Create default values
Always define default settings/values. These act as a fallback when settings don't exist yet (like on first install).
// utils/constants.js
export const DEFAULT_SETTINGS = {
isDark: false,
isEnabled: true,
userLang: 'english',
fontSize: 'medium',
extensionActive: true,
notificationsEnabled: true,
};
export const DEFAULT_USER_INFO = {
userEmail: '',
isAuthenticated: false,
creditsUsed: 0,
favCategories: [],
userId: undefined,
lastSync: undefined,
};
4. Creating Helper Functions
Never repeat chrome.storage.get() calls throughout your codebase. Instead, create reusable helper functions.
Reading from Storage
// utils/getStorage.js
import { DEFAULT_SETTINGS, DEFAULT_USER_INFO } from '@/utils/constants';
export async function getUserInfo() {
const result = await browser.storage.sync.get(['userInfo']);
if (result.userInfo) {
return result.userInfo;
}
/// return default to avoid bugs
return DEFAULT_USER_INFO;
}
export async function getUserSettings() {
const result = await browser.storage.sync.get(['userSettings']);
if (result.userSettings) {
return result.userSettings;
}
return DEFAULT_SETTINGS;
}
Writing to Storage
// utils/updateStorage.js
/// updating userInfo
export async function updateUserInfo(key, value) {
const result = await browser.storage.sync.get(['userInfo']);
if (!result.userInfo) {
console.log('❌ userInfo is empty');
return;
}
const newUserInfo = { ...result.userInfo, [key]: value };
await browser.storage.sync.set({
userInfo: newUserInfo,
});
}
/// updating settings
export async function updateUserSettings(key, value) {
const result = await browser.storage.sync.get(['userSettings']);
if (!result.userSettings) {
console.log('❌ userSettings is empty');
return;
}
const newUserSettings = { ...result.userSettings, [key]: value };
await browser.storage.sync.set({
userSettings: newUserSettings,
});
}
How this works:
- Fetch the current settings object
- Create a new object with the updated value using spread operator
- Save the new object back to storage
Initializing Storage on Install
Add this to your background script to set updefault values when users first install your extension:
// background.js or service-worker.js
browser.runtime.onInstalled.addListener((object) => {
if (object.reason === browser.runtime.OnInstalledReason.INSTALL) {
setDefaultSettings().catch((e) => {
console.log('failed to set default', e);
});
setDefaultUserInfo().catch((e) => {
console.log('failed to set default', e);
});
}
});
/// OnInstalledReason.INSTALL make sure that this only run on install and not on updates
5. Settings Context (for Popup, Sidebar)
Now comes the powerful part: making settings available throughout your React popup without prop drilling.
Create a Custom Hook
// hooks/useStorageData.jsx
import { useState, useEffect } from 'react';
import { DEFAULT_SETTINGS, DEFAULT_USER_INFO } from '@/utils/constants';
import { getUserSettings, getUserInfo } from '@/utils/getStorage';
import { updateUserInfo, updateUserSettings } from '@/utils/updateStorage';
export function useStorageData() {
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
const [userInfo, setUserInfo] = useState(DEFAULT_USER_INFO);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function fetchUserData() {
const userSettings = await getUserSettings();
setSettings(userSettings);
const userInfoStorage = await getUserInfo();
setUserInfo(userInfoStorage);
setLoading(false);
}
fetchUserData();
}, []);
function syncSettings(key, value) {
const newSettings = { ...settings, [key]: value };
updateUserSettings(key, value).then(() => setSettings(newSettings));
}
function syncUserInfo(key, value) {
const newUserInfo = { ...userInfo, [key]: value };
updateUserInfo(key, value).then(() => setUserInfo(newUserInfo));
}
return { settings, syncSettings, userInfo, syncUserInfo, loading };
}
Understanding the Code
1. Why initialize with default data?
const [settings, setSettings] = useState(DEFAULT_SETTINGS);
We use defaults as the initial state because:
- Your app needs an immediate value (can't be undefined)
- Prevents errors while data is loading from storage
- Provides sensible fallbacks if storage is empty
- The
useEffectwill replace these with real data once loaded
2. The useEffect Hook
useEffect(() => {
async function fetchUserData() {
const userSettings = await getUserSettings();
setSettings(userSettings);
// ... fetch userInfo ...
setLoading(false);
}
fetchUserData();
}, []);
This runs once when the component mounts (empty dependency array []):
- Fetches settings from storage
- Updates React state with the real values
- Sets loading to false once data is ready
3. The Magic of syncSettings
function syncSettings(key, value) {
const newSettings = { ...settings, [key]: value };
updateUserSettings(key, value).then(() => setSettings(newSettings));
}
This is incredibly useful because it:
- Updates storage (persists the change)
- Updates React state (triggers re-render)
- Instantly reflects changes across all components using this hook
- Provides optimistic updates (UI updates immediately, storage updates in background)
The same applies to syncUserInfo too.
6. Using Settings in Your Components
Now you can access settings from any component in your popup:
Example: Dark Mode Toggle
// pages/Settings.jsx
import { useStorageData } from '@/context/UserStorageData';
export default function Settings() {
const { settings, syncSettings, loading } = useStorageData();
if (loading) {
return <div>Loading settings...</div>;
}
return (
<div>
<h2>Settings</h2>
{/* Dark Mode Switch */}
<div className="setting-item">
<label htmlFor="dark-mode">Dark Mode</label>
<input
id="dark-mode"
type="checkbox"
checked={settings.isDark}
onChange={(e) => syncSettings('isDark', e.target.checked)}
/>
</div>
{/* Language Selector */}
<div className="setting-item">
<label htmlFor="language">Language</label>
<select
id="language"
value={settings.userLang}
onChange={(e) => syncSettings('userLang', e.target.value)}
>
<option value="english">English</option>
<option value="spanish">Spanish</option>
<option value="french">French</option>
</select>
</div>
</div>
);
}
Using Settings in Other Components
// pages/Home.jsx
import { useStorageData } from '@/context/UserStorageData';
export default function Home() {
const { settings, userInfo } = useStorageData();
return (
<div className={settings.isDark ? 'dark-theme' : 'light-theme'}>
<h1 style={{ fontSize: settings.fontSize }}>
Welcome back, {userInfo.userEmail || 'Guest'}!
</h1>
<p>Credits remaining: {userInfo.creditsUsed}/100</p>
{settings.notificationsEnabled && (
<div className="notification">
You have new updates!
</div>
)}
</div>
);
}
Notice how:
- ✅ No prop drilling needed
- ✅ Settings are automatically synchronized
- ✅ Changes in Settings page instantly reflect in Home page
hope you enjoyed it 💛
btw i created a complete boilerplate for extensions that contains auth, subscription and more... so you can launch your extension in days not weeks, it's called extFast
thankyou and
bye bye :)
Top comments (0)