DEV Community

Cover image for How to Implement Settings in Browser Extensions
Himanshu
Himanshu

Posted on

How to Implement Settings in Browser Extensions

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.sync everywhere

 

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' });

Enter fullscreen mode Exit fullscreen mode

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.storage instead of chrome.storage because i am using wxt framework to develop my extension , but you can easily replace browser with chrome keyword. 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"
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

To update mode, you'd need to:

  1. Fetch the entire settings object
  2. Navigate through nested properties
  3. Update the value
  4. 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']
  }

Enter fullscreen mode Exit fullscreen mode

 

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,
};

Enter fullscreen mode Exit fullscreen mode

 

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;
}
Enter fullscreen mode Exit fullscreen mode

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,
  });
}

Enter fullscreen mode Exit fullscreen mode

How this works:

  1. Fetch the current settings object
  2. Create a new object with the updated value using spread operator
  3. 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
Enter fullscreen mode Exit fullscreen mode

 

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 };
}

Enter fullscreen mode Exit fullscreen mode

Understanding the Code

1. Why initialize with default data?

const [settings, setSettings] = useState(DEFAULT_SETTINGS);
Enter fullscreen mode Exit fullscreen mode

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 useEffect will 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();
}, []);
Enter fullscreen mode Exit fullscreen mode

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));
}
Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

 
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)