Chrome Extension Options Page Tutorial
Give users a real settings screen — how to declare, build and persist a Chrome extension options page.

Once your extension has more than two or three settings, the popup gets cramped. The options page is the dedicated settings surface Chrome exposes from the extension's context menu and from chrome://extensions.
Declaring the page
Use "options_ui": { "page": "options.html", "open_in_tab": false } for an embedded dialog inside chrome://extensions, or set open_in_tab to true for a full tab. The legacy "options_page" key still works but gives you no control over presentation.
Building the form
- Render inputs in options.html with labels and sensible defaults.
- Load current values on DOMContentLoaded via chrome.storage.sync.get with a defaults object.
- Save on change or on a Save button click with chrome.storage.sync.set.
- Show a short 'Saved' confirmation so the action feels acknowledged.
- Validate before saving — clamp numbers, trim strings, reject empty required fields.
Keeping surfaces in sync
Listen for chrome.storage.onChanged in the popup and content scripts so a settings change applies immediately without a reload.
Opening it from your own UI
Call chrome.runtime.openOptionsPage() from a link in the popup so users can reach settings without digging through Chrome's menus.
Full page vs embedded dialog tradeoffs
With open_in_tab: false, Chrome renders options.html inside an iframe on chrome://extensions, which constrains width and hides your own page chrome — good for a handful of toggles. Set open_in_tab: true for anything with tables, multiple sections or a save/export flow, since it opens as a normal standalone tab you fully control.
Storing structured settings, not flat keys
Rather than many top-level chrome.storage keys, store one settings object: chrome.storage.sync.set({ settings: { theme: "dark", notifications: true } }). This keeps related fields atomic on read/write and makes it trivial to add a new field with a default without touching migration code.
Import / export settings as JSON
- Add an Export button that JSON.stringify()s the current settings object and triggers a download via a Blob URL.
- Add an Import button using a file input, parse the JSON, validate the shape, and call chrome.storage.sync.set().
- This gives power users a backup path and makes support requests easier — ask them to paste their exported JSON.
Resetting to defaults safely
Ship a DEFAULT_SETTINGS constant in a shared module imported by both the options page and the service worker. A 'Reset to defaults' button should call chrome.storage.sync.set(DEFAULT_SETTINGS) directly rather than chrome.storage.sync.clear(), which would also wipe unrelated keys another part of your extension may be using.
Common options page bugs
- Form fields showing stale values — you rendered before chrome.storage.sync.get() resolved.
- Settings not applying to already-open tabs — broadcast a message on save so active content scripts re-read storage instead of waiting for a reload.
- Options page throwing 'permission denied' errors — a permission you're checking for was declared as optional_permissions and was never granted.
Frequently asked questions
Can the options page use frameworks like React?
Yes, options.html is just a normal HTML page loaded inside the extension's origin, so any bundled framework works as long as the build output avoids remote script tags and inline event handlers, per MV3 CSP.
How do users find the options page if I don't link to it?
They can right-click the toolbar icon and choose 'Options', or open chrome://extensions, expand the extension's Details, and click 'Extension options'.
Is options_page still supported in Manifest V3?
Yes, the legacy "options_page" string key still works in V3 for backward compatibility, but it always opens in a new tab and offers no way to request the embedded dialog that options_ui supports.