Chrome Extension Development for Claude (2026)
Chrome’s incognito mode provides a privacy-focused browsing session that does not save history, cookies, or site data. However, many users expect their extensions to work smoothly in this mode, and developers need to understand how to handle incognito-specific behavior properly.
This guide covers the technical details of Chrome incognito extensions, including configuration options, API limitations, implementation patterns, and actionable advice for developers and power users alike.
Building Chrome Extensions with Claude Code
Claude Code excels at generating Chrome extension boilerplate, implementing Manifest V3 service workers, and handling the nuanced incognito mode configurations that trip up many developers. When you ask Claude Code to build a Chrome extension, understanding incognito behavior ensures your generated code works correctly across all browsing modes.
Key advantages of using Claude Code for Chrome extension development:
- Claude Code generates correct
manifest.jsonwith properincognitofield configuration - It handles the split vs spanning mode decision based on your extension’s requirements
- Claude Code writes service worker event handlers that correctly isolate incognito storage
- It generates test scripts that validate extension behavior in both normal and incognito contexts
When prompting Claude Code to build extensions that support incognito mode, include the incognito requirements in your initial prompt to avoid refactoring later.
Related Claude Code guides:
- Claude Code Chrome Extension Manifest V3 Guide
- Claude Code Testing Automation
- Claude Code JavaScript Best Practices
How Incognito Mode Affects Extensions
When a user opens an incognito window, Chrome applies specific rules to extensions by default:
- Extensions are disabled unless explicitly allowed by the user
- Separate storage is used for extension data in incognito contexts
- Background pages may behave differently depending on manifest version and
incognitomode setting - Some APIs have reduced functionality or require special handling
The default behavior blocks extensions from reading or modifying incognito sessions entirely. Users must manually grant permission for each extension they want active in incognito mode, either during installation or through chrome://extensions afterward.
This is intentional from Chrome’s perspective. Incognito is meant to be a clean-slate session. Extensions that silently run in incognito and persist data would undermine that expectation. Respecting this design, and being transparent about your extension’s incognito behavior, builds user trust.
The Three Incognito Modes Compared
The incognito field in your manifest controls how Chrome handles your extension in private sessions. Understanding the differences is critical before writing any code.
| Mode | Background Context | Storage | When to Use |
|---|---|---|---|
spanning (default) |
Single shared service worker for both regular and incognito | Separate storage per context | Most extensions |
split |
Separate background context for incognito | Fully isolated | Extensions needing distinct incognito behavior |
| (omitted) | Extension does not run in incognito | N/A | Extensions that must not run in private mode |
The split mode was more common in Manifest V2 with persistent background pages. In Manifest V3 with service workers, split creates a separate service worker instance for incognito sessions. This is resource-intensive and only worth using when your incognito functionality is fundamentally different from regular mode.
Configuring Extension Manifest
To support incognito mode, declare the incognito field in your manifest. Here is an example for Manifest V3:
{
"manifest_version": 3,
"name": "My Privacy Extension",
"version": "1.0",
"permissions": [
"storage",
"activeTab",
"scripting"
],
"incognito": "spanning"
}
Note that declaring "incognito": "spanning" does not automatically enable your extension in incognito, users still must allow it. The manifest field controls what happens once the user has granted access.
If you omit the incognito field entirely, Chrome treats it as spanning by default in most cases, but it is better to be explicit. Some extension review processes and documentation tools expect to see it declared.
Enabling Incognito Access. The User Side
From a user’s perspective, enabling an extension in incognito mode takes a few steps:
- Navigate to
chrome://extensions - Find the extension and click “Details”
- Toggle “Allow in Incognito” to on
Users can also right-click the extension icon in the toolbar and choose “Manage Extension” to reach the same settings. Power users who rely on extensions like ad blockers, password managers, or developer tools in incognito sessions need to do this manually for each extension.
As a developer, you can prompt users with instructions when your popup detects it is not allowed in incognito. You cannot programmatically enable incognito access, that would defeat the privacy model.
Detecting Incognito Mode in Your Extension
Your extension code can detect whether it is running in an incognito window by checking the tab’s incognito property:
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (chrome.runtime.lastError) {
console.error(chrome.runtime.lastError);
return;
}
const currentTab = tabs[0];
if (currentTab.incognito) {
console.log("Running in incognito mode");
// Adjust behavior accordingly
}
});
Use the async/await version for cleaner code in Manifest V3:
async function isIncognito() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
return tab?.incognito ?? false;
}
Alternatively, check whether the user has granted incognito permission to your extension at all:
chrome.extension.isAllowedIncognitoAccess((isAllowed) => {
if (isAllowed) {
console.log("Incognito access is enabled");
} else {
console.log("Incognito access is disabled - prompt user to enable");
}
});
This second check is useful in your popup to show a helpful message when the user opens your popup in incognito but has not granted access, instead of showing a broken UI, you can show clear instructions to enable it.
Storage Behavior in Incognito Mode
Storage is one of the most important, and most misunderstood, aspects of incognito extensions. Here is how each storage type behaves:
| Storage API | Incognito Behavior | Notes |
|---|---|---|
chrome.storage.local |
Separate storage for incognito context | Data written in incognito is not visible in regular mode and vice versa |
chrome.storage.sync |
Data does not sync between regular and incognito | Changes in incognito stay local to that context |
chrome.storage.session |
Cleared when the last incognito window closes | Ideal for per-session state that must not persist |
localStorage (web page) |
Cleared when incognito session ends | Not accessible from extension background |
| IndexedDB (page context) | Cleared when incognito session ends | Same as localStorage |
The separation of chrome.storage.local between regular and incognito contexts catches many developers off guard. A user’s saved preferences, authentication tokens, or extension settings in regular mode are not visible when that same extension runs in incognito. You need to decide whether to show default/empty state in incognito, or to read regular-mode settings as a fallback.
Here is a utility function that reads settings with a fallback approach:
async function getSettings() {
// Try incognito-context storage first
const incognitoSettings = await chrome.storage.local.get('settings');
if (Object.keys(incognitoSettings).length > 0) {
return incognitoSettings.settings;
}
// Fall back to default settings if nothing is stored for this context
return {
theme: 'system',
notifications: false,
analyticsEnabled: false,
};
}
Background Service Workers and Incognito
Manifest V3 uses service workers as background scripts. In incognito mode with spanning behavior, the same service worker handles both regular and incognito tabs. You need to check the context when handling events to route logic appropriately:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (sender.tab && sender.tab.incognito) {
// Handle incognito-specific logic
handleIncognitoMessage(message, sender, sendResponse);
} else {
// Handle regular browsing logic
handleRegularMessage(message, sender, sendResponse);
}
return true; // Keep message channel open for async response
});
With split behavior, Chrome creates a separate background context for incognito mode, which runs its own instance of your background script. The two instances cannot share in-memory state, communication between them requires using the messaging API or shared storage (which itself is subject to the storage isolation rules described above).
A practical implication of spanning mode: if your service worker caches API responses in memory, those cached values are shared across regular and incognito contexts. This is acceptable for non-sensitive data (e.g., a cached list of countries), but unacceptable for user-specific data (e.g., a cached authentication token). Design your in-memory state with this sharing in mind.
Practical Implementation Patterns
Pattern 1: Prompt Users to Enable Incognito Access
Rather than showing a broken popup when your extension is not allowed in incognito, detect the situation and guide the user:
async function checkIncognitoAccess() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (!tab?.incognito) return; // Not in incognito, nothing to check
chrome.extension.isAllowedIncognitoAccess((isAllowed) => {
if (!isAllowed) {
document.getElementById('main-content').style.display = 'none';
document.getElementById('incognito-prompt').style.display = 'block';
// Show instructions: go to chrome://extensions > Details > Allow in Incognito
}
});
}
document.addEventListener('DOMContentLoaded', checkIncognitoAccess);
Pattern 2: Disable Specific Features in Incognito
Not all features make sense in a privacy context. Analytics, sync, and persistent storage should typically be disabled:
function getFeatureFlags(incognito) {
return {
analytics: !incognito, // Disable analytics in incognito
syncData: !incognito, // Disable sync in incognito
persistentStorage: !incognito, // Use session storage only
backgroundFetch: !incognito, // Disable background operations
autoSave: !incognito, // Do not auto-save user data
};
}
async function initializeExtension() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const flags = getFeatureFlags(tab?.incognito ?? false);
if (!flags.analytics) {
disableAnalytics();
}
if (!flags.syncData) {
disableSync();
}
}
This approach makes it easy to audit which features are privacy-sensitive. The feature flags object documents the incognito behavior explicitly, which is useful for code reviews and security audits.
Pattern 3: Clear Sensitive Data on Incognito Exit
When the last incognito window closes, your extension should clean up any data it wrote during that session:
chrome.windows.onRemoved.addListener(async (windowId) => {
// Check if there are any remaining incognito windows
const incognitoWindows = await chrome.windows.getAll({ windowTypes: ['normal'] });
const hasIncognito = incognitoWindows.some(w => w.incognito);
if (!hasIncognito) {
// All incognito windows closed. clean up
await chrome.storage.session.clear();
console.log('Incognito session data cleared');
}
});
Using chrome.storage.session for incognito-specific transient data (rather than chrome.storage.local) is even better, since session storage is automatically cleared by Chrome when the incognito session ends, no manual cleanup needed.
Pattern 4: Visual Indicator for Incognito Mode
Show users clearly when your extension is running in an incognito context, especially when behavior differs:
async function updatePopupForContext() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
if (tab?.incognito) {
document.body.classList.add('incognito-mode');
document.getElementById('mode-badge').textContent = 'Private Mode';
document.getElementById('mode-badge').style.display = 'inline';
// Hide features not available in incognito
document.querySelectorAll('[data-requires="regular-mode"]').forEach(el => {
el.style.display = 'none';
});
}
}
In your CSS, the .incognito-mode class can apply a subtle visual treatment (a dark banner, a lock icon, a muted color scheme) that mirrors Chrome’s own incognito visual language, making the UI feel coherent with the browser experience.
Content Scripts in Incognito Mode
Content scripts run on web pages and are subject to the same incognito access restrictions as the rest of your extension. If your extension is allowed in incognito mode, content scripts will execute on incognito tabs as well.
One important consideration: content scripts have access to the DOM and can read page content, including URLs and page text. In incognito mode, users have a heightened expectation that their browsing is private. Content scripts that log page URLs, capture form content, or track user interaction should either be disabled in incognito or ensure that collected data is never persisted.
Here is a content script that checks its own context before doing anything sensitive:
// content-script.js
(function() {
// chrome.tabs is not available in content scripts. use the document URL instead
// For incognito checks, rely on the background service worker having sent context
chrome.runtime.sendMessage({ type: 'GET_CONTEXT' }, (response) => {
if (response?.incognito) {
// Restrict to read-only, non-logging behavior
initReadOnly();
} else {
initFull();
}
});
})();
Manifest V2 vs V3: What Changed for Incognito
Many articles about Chrome incognito extensions reference Manifest V2 patterns. Here is a summary of what changed:
| Feature | Manifest V2 | Manifest V3 |
|---|---|---|
| Background context | Persistent background page | Service worker (ephemeral) |
split mode |
Two persistent background pages | Two separate service worker instances |
| In-memory state | Persists for the session | is cleared when service worker goes idle |
chrome.extension.getBackgroundPage() |
Works for spanning to get shared context |
Removed; use chrome.runtime.getBackgroundPage() as Promise |
The shift to ephemeral service workers in Manifest V3 makes in-memory state sharing between regular and incognito contexts less reliable. Any state that needs to be shared across contexts must use chrome.storage rather than module-level variables.
User Experience Considerations
When designing extensions that work with incognito mode, consider these user experience factors:
Permission Requests: Users see a specific prompt when they first install an extension that supports incognito. Make sure your extension’s purpose is clear so users feel comfortable granting access. A password manager asking for incognito access makes intuitive sense; a coupon finder might raise more questions.
Visual Indicators: Add a clear visual indicator in your extension popup when running in incognito mode. Users should immediately understand why certain features are unavailable, rather than assuming the extension is broken.
Onboarding for Incognito: If your extension provides meaningful value in incognito (an ad blocker, for example), proactively suggest that the user enable incognito access during onboarding, but explain why, and let them decline without penalty.
Documentation: Clearly document which features work in incognito mode and which do not. Users deserve transparency about privacy-related behavior. A simple table in your extension’s README or Chrome Web Store description goes a long way.
Common Pitfalls to Avoid
- Assuming persistent storage: Data in
chrome.storage.sessionis cleared when incognito windows close. Data inchrome.storage.localis isolated per context. Do not assume a value written in regular mode will be readable in incognito. - Ignoring split mode complexity: If using
splitbehavior, remember you have two separate extension contexts that cannot share in-memory state. Debug each context independently using separate DevTools instances. - Missing error handling: Always check
chrome.runtime.lastErrorin callback-based APIs, or handle Promise rejections in async code. Incognito contexts can trigger edge cases that do not appear in regular mode. - Blocking incognito entirely without reason: Unless there is a valid security or compliance reason, blocking incognito access creates a poor user experience and may result in negative reviews. Handle incognito gracefully with reduced functionality rather than a hard block.
- Logging incognito URLs: Never log or transmit URLs, page titles, or user actions from incognito tabs to an external server. This violates user trust and may violate Chrome Web Store policies.
- Forgetting that
chrome.storage.localis context-isolated: This is the most common source of “my extension forgot my settings in incognito” bug reports. Default settings should be applied when storage reads return empty results in incognito.
Testing Incognito Behavior During Development
Testing incognito behavior requires a few specific steps that differ from regular extension development:
- Load your unpacked extension via
chrome://extensionswith Developer mode enabled - Click “Details” on your extension and enable “Allow in Incognito”
- Open an incognito window with
Ctrl+Shift+N(orCmd+Shift+Non macOS) - Open DevTools for the extension’s service worker via
chrome://serviceworker-internals - Use the “Inspect” link next to your extension’s service worker to open a DevTools panel
Note that in spanning mode, the service worker DevTools panel covers both regular and incognito contexts. You can filter log messages by checking tab.incognito in your logging code.
For split mode, Chrome creates a second service worker entry in chrome://serviceworker-internals with “(incognito)” in the name. Inspect each separately.
Conclusion
Chrome incognito extensions require thoughtful implementation to provide good user experience while respecting privacy expectations. By properly configuring your manifest, handling storage appropriately, detecting incognito context in your code, and giving users clear feedback about what works in private mode, you can build extensions that work smoothly in both regular and private browsing sessions.
The most important principle is transparency: users who choose incognito mode are making an active choice about their privacy. Your extension should honor that choice by minimizing data collection, clearly communicating limitations, and cleaning up after itself when the private session ends. Extensions that treat incognito as a first-class context, rather than an afterthought, build significantly more user trust and avoid the negative reviews that come from surprising privacy-related behavior.
Related Reading
- Best AI Chrome Extensions 2026: A Practical Guide for Developers
- Best Cookie Manager Chrome Extensions for Developers in 2026
- Best Developer Chrome Extensions 2026
Built by theluckystrike. More at zovo.one
Find the right skill → Browse 155+ skills in our Skill Finder.