Popup Interface Design and Options Storage State

When users install your extension, the most frequent interaction point is the icon in the top-right corner of the browser. The small window that pops up when clicking this icon is called Popup. For advanced tasks like account binding or parameter adjustments, users are typically directed to a full-screen standalone page called Options Page.

In this lesson, we will learn how to design these two critical UI interfaces and enable them to remember user settings.

1. Designing an Elegant Popup Window

In manifest.json, we use action.default_popup to specify the HTML file for the popup window.

This HTML file is essentially identical to regular web development. You can introduce CSS frameworks (like Tailwind CSS via CDN or bundled files) or even use frontend frameworks like React/Vue.

[!WARNING] CSP (Content Security Policy) Restrictions
Chrome Extensions have strict security limitations. You cannot directly write inline scripts like <script> alert(1); </script> in HTML. All JavaScript must be imported as external files (<script src="popup.js"></script>).

Dynamically Fetching Current Tab Information

A common Popup feature is performing actions based on "the webpage the user is currently viewing." To fetch the current tab's information, use the chrome.tabs API:

// popup.js
document.addEventListener('DOMContentLoaded', async () => {
  // Query the currently active tab in the current window
  const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
  
  if (tab) {
    const urlDisplay = document.getElementById('url-display');
    urlDisplay.innerText = tab.url; // Display the URL in the Popup interface
  }
});

Note: Calling this API requires the "activeTab" or "tabs" permission in the manifest.

2. Implementing the Options Settings Page

If your extension requires users to input an API Key, select a color theme, or configure a blocklist, a small Popup won't suffice. You need to create a dedicated settings page.

First, declare the Options page in manifest.json:

{
  "options_ui": {
    "page": "options.html",
    "open_in_tab": true
  }
}

Setting open_in_tab: true ensures the page opens in a new full-sized tab, significantly improving the user experience.

3. Using chrome.storage to Persist User Preferences

Whether in the Popup or Options page, when users modify settings, these changes must be saved to ensure they persist after the browser is reopened.

In extensions, we do not use localStorage because it can be easily deleted during browser data clearance and is difficult to share across different contexts (Popup, Content Script, Background).

Instead, we use the chrome.storage API, which offers two primary modes:

  • chrome.storage.local: Data is stored only on the current device. Capacity is capped at 5MB (expandable upon request).
  • chrome.storage.sync: Data is automatically synced across the user's devices via their Google account! Capacity is smaller (100KB), making it ideal for storing API Keys or simple boolean settings.

Practical Example: Saving and Retrieving an API Key

Hereโ€™s a simple Options page logic example:

Saving Data (Writing to Sync Storage):

// options.js
const saveBtn = document.getElementById('save-btn');
const apiKeyInput = document.getElementById('api-key-input');

saveBtn.addEventListener('click', () => {
  const userApiKey = apiKeyInput.value;
  
  // Save data to sync storage
  chrome.storage.sync.set({ openAiApiKey: userApiKey }, () => {
    console.log('Settings saved!');
    // Display a confirmation message to the user
    const status = document.getElementById('status-msg');
    status.innerText = 'Saved successfully!';
    setTimeout(() => { status.innerText = ''; }, 2000);
  });
});

Loading Data During Page Initialization:

// When the Options page loads, retrieve the previously saved Key and display it in the input field
document.addEventListener('DOMContentLoaded', () => {
  chrome.storage.sync.get(['openAiApiKey'], (result) => {
    if (result.openAiApiKey) {
      document.getElementById('api-key-input').value = result.openAiApiKey;
    }
  });
});

Listening for Storage Changes

The most powerful feature of this API is that it allows other components (e.g., your Content Script) to listen for setting changes in real time!

For example, if a user toggles "Dark Mode" in the Options page, your Content Script can immediately respond and adjust the webpage colors:

// In content.js
chrome.storage.onChanged.addListener((changes, namespace) => {
  for (let [key, { oldValue, newValue }] of Object.entries(changes)) {
    if (key === 'darkMode') {
      console.log(`Dark mode changed from ${oldValue} to ${newValue}`);
      // Execute UI color change logic
      toggleDarkModeUI(newValue);
    }
  }
});

Popup UI Best Practices

| Practice | Reason | |----------|--------| | Keep popup under 600x400 | Chrome limits popup size | | Use storage.sync for settings | Settings sync across devices | | Use storage.local for large data | Local storage has higher quota (10MB vs 100KB) | | Communicate with background via messaging | Direct function calls don't work across contexts | | Handle loading states | storage.get() is async โ€” show loading spinner | | Save automatically on change | Don't require a "Save" button | | Use browser action badge for status | Show active/inactive state on the icon |

Storage Types

| Type | Quota | Persistence | Sync | Use Case | |------|-------|-------------|------|----------| | storage.sync | 100KB | Yes | Across devices | User preferences, settings | | storage.local | 10MB+ | Yes | Local only | Cached data, history | | storage.session | 10MB | Session only | Local only | Temporary state | | storage.managed | Read-only | Yes | Admin config | Enterprise policies |

Popup HTML Structure

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="popup.css">
</head>
<body>
  <div class="popup-container">
    <header>
      <h1>My Extension</h1>
      <span id="status-badge" class="badge active">Active</span>
    </header>
    
    <main>
      <div class="setting-row">
        <label for="toggle-feature">Enable Feature</label>
        <input type="checkbox" id="toggle-feature">
      </div>
      
      <div class="setting-row">
        <label for="color-picker">Accent Color</label>
        <input type="color" id="color-picker" value="#22c55e">
      </div>
      
      <div class="setting-row">
        <label for="refresh-interval">Auto Refresh (min)</label>
        <input type="number" id="refresh-interval" value="5" min="1" max="60">
      </div>
      
      <div class="stats-row">
        <div class="stat">
          <span class="stat-value" id="item-count">0</span>
          <span class="stat-label">Items Tracked</span>
        </div>
        <div class="stat">
          <span class="stat-value" id="alerts-count">0</span>
          <span class="stat-label">Active Alerts</span>
        </div>
      </div>
    </main>
    
    <footer>
      <button id="view-dashboard">Dashboard</button>
      <a href="#" id="open-options">Settings</a>
    </footer>
  </div>
  <script src="popup.js"></script>
</body>
</html>

Popup CSS

body {
  width: 320px;
  min-height: 400px;
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 14px;
  color: #1a1a2e;
  background: #ffffff;
}

.popup-container {
  padding: 16px;
}

header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
  padding-bottom: 12px;
  border-bottom: 1px solid #e5e7eb;
}

h1 {
  font-size: 18px;
  margin: 0;
  font-weight: 600;
}

.badge {
  padding: 2px 8px;
  border-radius: 12px;
  font-size: 12px;
  font-weight: 500;
}

.badge.active {
  background: #dcfce7;
  color: #166534;
}

.badge.inactive {
  background: #fee2e2;
  color: #991b1b;
}

.setting-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 12px;
}

.stats-row {
  display: flex;
  gap: 12px;
  margin: 20px 0;
}

.stat {
  flex: 1;
  text-align: center;
  padding: 12px;
  background: #f9fafb;
  border-radius: 8px;
}

.stat-value {
  display: block;
  font-size: 24px;
  font-weight: 700;
  color: #22c55e;
}

.stat-label {
  font-size: 12px;
  color: #6b7280;
  margin-top: 4px;
}

footer {
  display: flex;
  gap: 8px;
  padding-top: 12px;
  border-top: 1px solid #e5e7eb;
}

footer button, footer a {
  flex: 1;
  text-align: center;
  padding: 8px;
  border-radius: 6px;
  font-size: 13px;
  text-decoration: none;
  cursor: pointer;
}

footer button {
  background: #22c55e;
  color: white;
  border: none;
}

footer a {
  background: #f3f4f6;
  color: #374151;
}

Options Page

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Extension Settings</title>
  <link rel="stylesheet" href="options.css">
</head>
<body>
  <div class="options-container">
    <h1>Extension Settings</h1>
    
    <section>
      <h2>General</h2>
      <div class="option-row">
        <label for="theme">Theme</label>
        <select id="theme">
          <option value="light">Light</option>
          <option value="dark">Dark</option>
          <option value="system">System</option>
        </select>
      </div>
    </section>
    
    <section>
      <h2>Notifications</h2>
      <div class="option-row">
        <label>
          <input type="checkbox" id="notif-enabled">
          Enable Desktop Notifications
        </label>
      </div>
      <div class="option-row">
        <label for="notif-sound">Notification Sound</label>
        <select id="notif-sound">
          <option value="chime">Chime</option>
          <option value="bell">Bell</option>
          <option value="none">Silent</option>
        </select>
      </div>
    </section>
    
    <section>
      <h2>Data</h2>
      <div class="option-row">
        <button id="export-data" class="btn-secondary">Export Data</button>
        <button id="clear-data" class="btn-danger">Clear All Data</button>
      </div>
      <p class="hint">Data is stored locally and synced across devices.</p>
    </section>
    
    <div class="actions">
      <button id="save-settings" class="btn-primary">Save Settings</button>
      <span id="save-status"></span>
    </div>
  </div>
  <script src="options.js"></script>
</body>
</html>

Summary

The popup is the user's primary interface to your extension. Options page handles advanced settings. Use chrome.storage.sync for preferences that should sync across devices, and chrome.storage.local for larger data. Design both pages with a clean, responsive layout.

Key takeaways:

  • Popup: quick actions, status display, basic settings
  • Options: detailed configuration, data management
  • storage.sync: 100KB, syncs across Chrome devices
  • storage.local: 10MB+, local only, for large data
  • storage.session: temporary data for the session
  • Popup size: Chrome limits to ~600x400
  • Use messaging for popupโ†’background communication
  • Save settings automatically on change
  • Show status badge on the browser icon
  • Design options as a proper HTML page (not in popup)

What's Next: Monetization & Publishing

The next chapter covers monetizing and publishing your extension โ€” pricing models, Stripe integration, Chrome Web Store listing, and marketing.

Unlock Full Tutorial

This chapter is paid content. Join the project to unlock over 5000 words of deep analysis, including 10+ god-tier Prompts and real Source Code examples!