The chrome.* namespace is what makes Chrome extensions powerful. Standard webpages can manipulate their own DOM and make network requests, but they cannot read open tabs, intercept navigation, manage bookmarks, or run code in other contexts. Chrome extensions can. Every privileged capability is exposed through a specific chrome.* API.
This reference documents every commonly-used Chrome extension API with its full surface, a working code example, the context it's available in (background, content script, popup, options), and the practical "when to use this" guidance Chrome's official docs don't provide.
For the manifest fields that grant access to these APIs, see the Manifest V3 reference. For the broader architectural context of how extensions work, see the complete guide.
How to read this reference
Each API entry has the same structure:
- Permission required — the manifest permission needed to use it
- Available in — which contexts can call this API (background, content script, popup, options, all)
- What it does — one-paragraph explanation
- Common methods — the methods you'll actually use, with signatures
- Example — runnable code showing a realistic use case
- When to use it — the situations this API is the right tool for
- When not to use it — common misuses or better alternatives
APIs are grouped by category, not alphabetical. Storage and runtime first because every extension needs them, then UI and user interaction, then navigation and tabs, then network and background tasks, then specialized APIs.
Storage APIs
chrome.storage
Permission required: storage
Available in: background, content scripts, popup, options
The canonical way to persist data in a Chrome extension. Three sub-namespaces: chrome.storage.local, chrome.storage.sync, and chrome.storage.session.
chrome.storage.local stores data on the local device. Persists across browser restarts. Default quota: 10MB, or unlimited with the unlimitedStorage permission.
chrome.storage.sync works the same way but syncs across Chrome installs signed into the same Google account. Strict quota: ~100KB total, 8KB per item, rate-limited to a few writes per hour per key.
chrome.storage.session stores data in memory only. Persists across service worker restarts but is cleared when Chrome closes. Useful for ephemeral state that's expensive to recompute.
Common methods:
chrome.storage.local.get(keys, callback)
chrome.storage.local.get(keys) // returns Promise
chrome.storage.local.set({ key: value }, callback)
chrome.storage.local.set({ key: value }) // returns Promise
chrome.storage.local.remove(keys)
chrome.storage.local.clear()
chrome.storage.onChanged.addListener((changes, areaName) => { ... })
Example:
// Save a user preference
await chrome.storage.local.set({ theme: 'dark' });
// Read it back later
const { theme } = await chrome.storage.local.get(['theme']);
console.log(theme); // 'dark'
// Save with multiple keys
await chrome.storage.local.set({
theme: 'dark',
language: 'en',
savedItems: ['item1', 'item2', 'item3']
});
// Listen for changes from anywhere
chrome.storage.onChanged.addListener((changes, areaName) => {
if (areaName === 'local' && changes.theme) {
console.log('Theme changed:', changes.theme.newValue);
}
});
When to use it: every extension. Even trivial ones store at least user preferences. local for everything you can fit, sync only for small user-facing preferences that benefit from cross-device sync, session for ephemeral state.
When not to use it:
- For very large datasets (hundreds of MB), use IndexedDB instead — it's faster for big data.
- For data shared between multiple users (not just devices), use your own backend (Supabase, Firebase). chrome.storage.sync is per-account, not multi-user.
- Don't store secrets like API keys in chrome.storage.sync — they sync via Google's servers and could be exposed if the account is compromised.
chrome.storage rate limits and gotchas
Two gotchas worth highlighting:
chrome.storage.sync has aggressive rate limits: typically 120 writes/minute per extension, with per-key limits stricter. If you write rapidly, calls silently fail and the data doesn't sync. Solution: debounce sync writes, only sync data the user actually needs across devices.
chrome.storage.local quota errors are silent by default. If you exceed 10MB without unlimitedStorage, writes start failing without throwing. Always check the result of write operations in code that handles large data:
try {
await chrome.storage.local.set({ largeData: bigArray });
} catch (e) {
console.error('Storage write failed:', e);
// Handle quota exceeded
}
Messaging and runtime APIs
chrome.runtime
Permission required: None (always available) Available in: background, content scripts, popup, options
The runtime API is how extension contexts communicate. Every extension uses it for messaging, getting URLs of extension resources, reading the manifest, and reacting to install/update events.
Common methods:
chrome.runtime.sendMessage(message, callback)
chrome.runtime.sendMessage(message) // returns Promise
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// sendResponse must be called within ~5 minutes or the channel times out
return true; // Return true to keep channel open for async sendResponse
});
chrome.runtime.connect({ name: 'channel-name' })
chrome.runtime.onConnect.addListener((port) => { ... })
chrome.runtime.getURL(path) // Get full URL of extension resource
chrome.runtime.getManifest() // Read manifest as object
chrome.runtime.id // Extension's ID
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') { /* first install */ }
if (details.reason === 'update') { /* updated */ }
});
Example — one-shot messaging:
// Content script — request data from background
const response = await chrome.runtime.sendMessage({
type: 'FETCH_USER_DATA',
userId: 42
});
console.log('User data:', response);
// Background worker — handle the request
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'FETCH_USER_DATA') {
fetchUser(message.userId).then(user => sendResponse(user));
return true; // Keep channel open for async response
}
});
Example — long-lived connections:
// Popup — open a port to background for streaming updates
const port = chrome.runtime.connect({ name: 'live-stats' });
port.onMessage.addListener((stats) => {
document.getElementById('count').textContent = stats.count;
});
// Background — push updates over the port
chrome.runtime.onConnect.addListener((port) => {
if (port.name === 'live-stats') {
const interval = setInterval(() => {
port.postMessage({ count: Math.random() });
}, 1000);
port.onDisconnect.addListener(() => clearInterval(interval));
}
});
Example — reacting to install:
chrome.runtime.onInstalled.addListener((details) => {
if (details.reason === 'install') {
// Set defaults on first install
chrome.storage.local.set({
theme: 'system',
enabled: true
});
// Open onboarding page
chrome.tabs.create({ url: chrome.runtime.getURL('onboarding.html') });
} else if (details.reason === 'update') {
const previousVersion = details.previousVersion;
console.log(`Updated from ${previousVersion} to ${chrome.runtime.getManifest().version}`);
}
});
When to use it:
- Any time two extension contexts need to talk
- For one-shot request/response patterns, use
sendMessage - For ongoing streams or back-and-forth, use
connect+ ports - For first-install logic (defaults, onboarding), use
onInstalled
When not to use it:
- Don't poll messages — use ports for streaming
- Don't rely on
sendResponsebeing called synchronously; returntrueif doing async work - Don't message between content scripts in different tabs directly — route through the background worker
chrome.runtime gotchas
The "return true" rule. If your onMessage listener does any async work before calling sendResponse, you must return true from the listener. Otherwise the channel closes immediately and sendResponse becomes a no-op. This is the single most common Chrome extension bug.
Service worker termination. In Manifest V3, the background service worker can be terminated by Chrome when idle. If you store state in a module-level variable in the background, it disappears across terminations. Use chrome.storage for anything that needs to persist between events.
Message size limits. Messages are serialized to JSON. Large objects (multi-MB) cause performance issues or fail outright. For large data transfers, store in chrome.storage and send only a key over the message.
Tab and window APIs
chrome.tabs
Permission required: tabs for most methods, activeTab for narrower access
Available in: background, popup, options (NOT content scripts)
Manage browser tabs — read URLs and titles, switch tabs, open new tabs, close tabs, send messages to specific tabs.
Common methods:
chrome.tabs.query(queryInfo) // Find tabs matching criteria
chrome.tabs.get(tabId) // Get details of specific tab
chrome.tabs.create({ url }) // Open a new tab
chrome.tabs.update(tabId, { url }) // Navigate or modify a tab
chrome.tabs.remove(tabIds) // Close tabs
chrome.tabs.sendMessage(tabId, message) // Send to content script in that tab
chrome.tabs.onCreated.addListener(tab => { ... })
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => { ... })
chrome.tabs.onActivated.addListener(activeInfo => { ... })
chrome.tabs.onRemoved.addListener((tabId, removeInfo) => { ... })
Example — find and modify tabs:
// Find the current active tab
const [activeTab] = await chrome.tabs.query({
active: true,
currentWindow: true
});
console.log('Current URL:', activeTab.url);
// Find all GitHub tabs and close them
const githubTabs = await chrome.tabs.query({
url: 'https://github.com/*'
});
await chrome.tabs.remove(githubTabs.map(t => t.id));
// Open a new tab next to the current one
await chrome.tabs.create({
url: 'https://example.com',
index: activeTab.index + 1
});
Example — message a specific tab's content script:
// From background, push to content script in specific tab
chrome.tabs.sendMessage(activeTab.id, {
type: 'HIGHLIGHT_KEYWORD',
keyword: 'manifest'
});
When to use it:
- Tab managers, tab switchers, tab-based productivity tools
- Triggering page-level actions from the background or popup
- Opening specific pages (onboarding, settings, results)
- Bulk operations (close all tabs matching a pattern)
When not to use it:
- Don't use full
tabspermission whenactiveTabsuffices.activeTabgives access to the current tab when the user clicks your extension, with no permission warning. - Don't iterate all tabs on every event — query specifically.
- Content scripts can't use
chrome.tabs— route through the background.
chrome.windows
Permission required: tabs for full access
Available in: background, popup, options
Manage browser windows. Create new windows (including popup windows), focus existing ones, query window state.
Common methods:
chrome.windows.create({ url, type, width, height })
chrome.windows.get(windowId)
chrome.windows.getAll(getInfo)
chrome.windows.getCurrent()
chrome.windows.update(windowId, updateInfo)
chrome.windows.remove(windowId)
Example — open a custom popup window:
chrome.windows.create({
url: chrome.runtime.getURL('custom-window.html'),
type: 'popup',
width: 400,
height: 600
});
When to use it:
- Multi-window workflows (a sidebar tool that runs in its own window)
- Custom-sized popups that don't fit Chrome's standard popup constraint (max 800x600)
- Window-level controls (minimize, focus, resize)
When not to use it:
- For the extension's default toolbar popup, use the
action.default_popupmanifest field instead - For a settings page, use
chrome.runtime.openOptionsPage()instead of manually creating a window
chrome.action
Permission required: None (built-in to manifest action) Available in: background, popup, options
Control the toolbar icon — change the icon, set badges, enable/disable per tab, open the popup programmatically.
Common methods:
chrome.action.setIcon({ path: 'icon.png' })
chrome.action.setBadgeText({ text: '5' })
chrome.action.setBadgeBackgroundColor({ color: '#FF0000' })
chrome.action.setTitle({ title: 'Updated tooltip' })
chrome.action.disable(tabId)
chrome.action.enable(tabId)
chrome.action.openPopup() // Programmatically open the popup (limited contexts)
Example — show notification badge:
// When new items are saved, show count on the icon
chrome.storage.onChanged.addListener((changes) => {
if (changes.unreadCount) {
chrome.action.setBadgeText({
text: changes.unreadCount.newValue.toString()
});
chrome.action.setBadgeBackgroundColor({ color: '#FF6B6B' });
}
});
// Clear badge when popup opens
chrome.action.onClicked.addListener(() => {
chrome.action.setBadgeText({ text: '' });
});
When to use it:
- Visual feedback (badge counts, icon state)
- Disabling the icon on pages where the extension doesn't apply
- Indicating state (enabled/disabled, success/error) via icon changes
When not to use it:
- Don't change the icon constantly — it's visually distracting
- Don't use badges for ephemeral state — badges persist across browser restarts unless explicitly cleared
Scripting and DOM APIs
chrome.scripting
Permission required: scripting plus host_permissions for target URLs
Available in: background, popup, options
Programmatic content script injection. The V3 replacement for chrome.tabs.executeScript (which is removed). Use this when you need to inject scripts conditionally rather than via the manifest's content_scripts declaration.
Common methods:
chrome.scripting.executeScript({
target: { tabId },
func: () => { /* runs in page context */ },
args: [arg1, arg2]
})
chrome.scripting.executeScript({
target: { tabId },
files: ['injected.js']
})
chrome.scripting.insertCSS({
target: { tabId },
css: '.my-class { color: red; }'
})
chrome.scripting.removeCSS({
target: { tabId },
css: '.my-class { color: red; }'
})
chrome.scripting.registerContentScripts([{
id: 'dynamic-script',
matches: ['https://example.com/*'],
js: ['content.js'],
runAt: 'document_idle'
}])
Example — inject script when user clicks toolbar icon:
chrome.action.onClicked.addListener(async (tab) => {
// Inject a script that highlights all paragraphs
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => {
document.querySelectorAll('p').forEach(p => {
p.style.backgroundColor = 'yellow';
});
}
});
});
Example — pass arguments to injected function:
chrome.scripting.executeScript({
target: { tabId },
func: (color, selector) => {
document.querySelectorAll(selector).forEach(el => {
el.style.backgroundColor = color;
});
},
args: ['lightblue', '.note']
});
When to use it:
- Conditional injection (run script only when user triggers, not on every matching page)
- Dynamic script content (script depends on user input or runtime state)
- Injecting into specific frames
- Programmatic CSS injection that can also be removed
When not to use it:
- For "always inject on these URLs" use
content_scriptsin the manifest — simpler and faster - The
funcyou pass is serialized and sent to the page; it can't reference closure variables from the calling context. Pass everything viaargs.
chrome.declarativeContent (deprecated for most uses)
Mostly replaced by chrome.action for icon control and chrome.scripting for conditional injection. Avoid using.
Notifications and user interaction APIs
chrome.notifications
Permission required: notifications
Available in: background, popup, options
Show desktop notifications outside the browser. Useful for alerts when Chrome isn't focused.
Common methods:
chrome.notifications.create(notificationId, options, callback)
chrome.notifications.update(notificationId, options)
chrome.notifications.clear(notificationId)
chrome.notifications.getAll(callback)
chrome.notifications.onClicked.addListener(notificationId => { ... })
chrome.notifications.onButtonClicked.addListener((notificationId, buttonIndex) => { ... })
chrome.notifications.onClosed.addListener((notificationId, byUser) => { ... })
Example — show a notification with action buttons:
chrome.notifications.create('save-success', {
type: 'basic',
iconUrl: chrome.runtime.getURL('icons/icon-128.png'),
title: 'Saved!',
message: 'Article saved to your reading list.',
buttons: [
{ title: 'View saved items' },
{ title: 'Dismiss' }
],
priority: 1
});
chrome.notifications.onButtonClicked.addListener((id, buttonIndex) => {
if (id === 'save-success' && buttonIndex === 0) {
chrome.tabs.create({ url: chrome.runtime.getURL('saved.html') });
}
});
When to use it:
- Background alerts (sync completed, error occurred, alarm fired)
- User actions outside the browser window
- Status updates the user should see immediately
When not to use it:
- Don't spam users with notifications. Each one trains them to ignore your extension. Notifications should be rare and important.
- For in-page feedback, use a content-script DOM toast instead. Browser notifications break focus.
- On macOS and Windows, notifications require user permission. Ask permission only when needed.
chrome.contextMenus
Permission required: contextMenus
Available in: background only
Add items to the browser's right-click menu. Items can appear in the page context, link context, image context, selection context, or extension's own action context.
Common methods:
chrome.contextMenus.create({ id, title, contexts, parentId })
chrome.contextMenus.update(id, properties)
chrome.contextMenus.remove(id)
chrome.contextMenus.removeAll()
chrome.contextMenus.onClicked.addListener((info, tab) => { ... })
Example — add a context menu item for selected text:
chrome.runtime.onInstalled.addListener(() => {
chrome.contextMenus.create({
id: 'summarize-selection',
title: 'Summarize "%s"',
contexts: ['selection']
});
});
chrome.contextMenus.onClicked.addListener((info, tab) => {
if (info.menuItemId === 'summarize-selection') {
const selectedText = info.selectionText;
summarizeText(selectedText, tab.id);
}
});
The %s token in the title is replaced with the selected text at display time.
When to use it:
- Actions that operate on selected text, links, or images
- Quick-access commands without requiring a popup
- Page-level actions discoverable through right-click
When not to use it:
- Don't add too many items. Users find cluttered context menus frustrating.
- Don't add context menus for actions that already have a clearer trigger (toolbar icon, keyboard shortcut).
chrome.commands
Permission required: None (declared in manifest) Available in: background only
Handle keyboard shortcuts declared in the manifest's commands field.
Common methods:
chrome.commands.onCommand.addListener((command, tab) => { ... })
chrome.commands.getAll(callback) // List all registered commands
Example — handle a shortcut:
chrome.commands.onCommand.addListener(async (command, tab) => {
if (command === 'toggle-highlight') {
await chrome.scripting.executeScript({
target: { tabId: tab.id },
func: () => document.body.classList.toggle('highlight-mode')
});
}
});
When to use it:
- Power-user shortcuts for common actions
- The
_execute_actionspecial command for opening the popup via keyboard - Global shortcuts (
global: truein manifest) that work when Chrome isn't focused
When not to use it:
- Don't declare more than 4 suggested shortcuts. Chrome limits the suggested-key set; extras default to unconfigured and are easy to miss.
Network and request APIs
chrome.declarativeNetRequest
Permission required: declarativeNetRequest or declarativeNetRequestWithHostAccess
Available in: background
The V3 replacement for blocking webRequest. Block, redirect, or modify network requests using a declarative rules engine. Rules are evaluated by Chrome natively, faster and more privacy-respecting than JavaScript-based blocking.
Common methods:
// Declare static rules in manifest, then dynamically modify:
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [1, 2],
addRules: [
{
id: 100,
priority: 1,
action: { type: 'block' },
condition: {
urlFilter: 'tracker.example.com',
resourceTypes: ['script', 'xmlhttprequest']
}
}
]
})
chrome.declarativeNetRequest.updateEnabledRulesets({
enableRulesetIds: ['ruleset_1'],
disableRulesetIds: ['ruleset_2']
})
Example — block a tracking domain:
chrome.declarativeNetRequest.updateDynamicRules({
addRules: [{
id: 1,
priority: 1,
action: { type: 'block' },
condition: {
urlFilter: '||tracker-domain.com',
resourceTypes: ['script', 'image', 'xmlhttprequest']
}
}]
});
When to use it:
- Ad blockers
- Privacy tools that block trackers
- Custom request modifications (rewriting headers, redirecting)
- Anywhere you previously used blocking webRequest
When not to use it:
- For one-off observation (not blocking), use
chrome.webRequestinstead — simpler - Limits: 30,000 static rules per ruleset, 100 rulesets total, 5,000 dynamic rules. Plan accordingly for large rule sets.
chrome.webRequest
Permission required: webRequest plus host_permissions
Available in: background
Observe network requests as they happen. In V3, this is read-only — you cannot block or modify requests from a webRequest listener (use declarativeNetRequest for that). Still useful for analytics, debugging, or extensions that just need to know about requests.
Common methods:
chrome.webRequest.onBeforeRequest.addListener(
details => { /* observe */ },
{ urls: ['<all_urls>'], types: ['main_frame', 'xmlhttprequest'] }
)
chrome.webRequest.onCompleted.addListener(details => { /* observe */ })
chrome.webRequest.onErrorOccurred.addListener(details => { /* observe */ })
Example — count API calls on a domain:
let apiCallCount = 0;
chrome.webRequest.onCompleted.addListener(
(details) => {
if (details.url.startsWith('https://api.example.com/')) {
apiCallCount++;
console.log(`API calls today: ${apiCallCount}`);
}
},
{ urls: ['https://api.example.com/*'] }
);
When to use it:
- Network monitoring extensions
- Debugging tools that surface failed or slow requests
- Analytics about page resource loading
When not to use it:
- Cannot block or modify in V3. Use declarativeNetRequest instead.
- High-frequency event — don't do expensive work in the listener.
chrome.cookies
Permission required: cookies plus host_permissions
Available in: background, popup, options
Read and write browser cookies for declared host permissions.
Common methods:
chrome.cookies.get({ url, name }, callback)
chrome.cookies.getAll({ domain }, callback)
chrome.cookies.set({ url, name, value, expirationDate })
chrome.cookies.remove({ url, name })
chrome.cookies.onChanged.addListener(changeInfo => { ... })
Example — read a session cookie:
const cookie = await chrome.cookies.get({
url: 'https://example.com',
name: 'session_token'
});
if (cookie) {
console.log('Session active:', cookie.value);
}
When to use it:
- Auth flows that need to read existing cookies (e.g., extension uses the user's logged-in state)
- Cookie auditing tools
- Cross-domain workflows where reading a cookie identifies the user
When not to use it:
- This is a high-trust permission with privacy implications. Web Store reviewers scrutinize cookie usage. Only request it when genuinely needed and document the reason in your Web Store listing.
Background task APIs
chrome.alarms
Permission required: alarms
Available in: background
Schedule callbacks to run periodically or at a specific time. The reliable way to run background tasks in MV3, since service workers can be terminated and won't run setInterval reliably.
Common methods:
chrome.alarms.create(name, { delayInMinutes, periodInMinutes, when })
chrome.alarms.clear(name)
chrome.alarms.clearAll()
chrome.alarms.get(name, callback)
chrome.alarms.getAll(callback)
chrome.alarms.onAlarm.addListener(alarm => { ... })
Example — sync data every 30 minutes:
// On install, create the alarm
chrome.runtime.onInstalled.addListener(() => {
chrome.alarms.create('sync-data', {
periodInMinutes: 30
});
});
// Listen for the alarm firing
chrome.alarms.onAlarm.addListener(async (alarm) => {
if (alarm.name === 'sync-data') {
await syncToBackend();
}
});
When to use it:
- Periodic syncing
- Scheduled cleanups (clear old data once a day)
- Reminders ("save this for later" with a notification in 1 hour)
- Anything that needs to run reliably in the background
When not to use it:
- Don't use for sub-minute intervals — Chrome enforces minimum 1-minute periods (30 seconds in development mode)
- Don't use as a replacement for event-driven logic. If you can trigger work in response to a real event (tab loaded, message received), that's better than polling.
chrome.idle
Permission required: idle
Available in: background
Detect when the user has been idle or returned to active.
Common methods:
chrome.idle.queryState(detectionIntervalInSeconds, callback)
chrome.idle.setDetectionInterval(intervalInSeconds)
chrome.idle.onStateChanged.addListener(newState => {
// newState: 'active', 'idle', or 'locked'
})
Example — pause tracking when user goes idle:
chrome.idle.setDetectionInterval(60); // 1 minute
chrome.idle.onStateChanged.addListener((state) => {
if (state === 'idle' || state === 'locked') {
pauseTimeTracking();
} else if (state === 'active') {
resumeTimeTracking();
}
});
When to use it:
- Time tracking extensions
- Focus tools that detect user attention
- Sync logic that should pause when the user is away
When not to use it:
- For ephemeral checks, prefer event-driven logic (page focus events, mouse movement) since those don't require this permission.
Specialized APIs
chrome.identity
Permission required: identity
Available in: background, popup, options
OAuth flows for Google services and other providers. Handles the complexity of redirect URLs, token storage, and refresh tokens.
Common methods:
chrome.identity.getAuthToken({ interactive: true }, token => { ... })
chrome.identity.removeCachedAuthToken({ token })
chrome.identity.launchWebAuthFlow({ url, interactive: true }, redirectUrl => { ... })
chrome.identity.getProfileUserInfo(userInfo => { ... })
Example — Google OAuth flow:
async function getGoogleToken() {
return new Promise((resolve, reject) => {
chrome.identity.getAuthToken({ interactive: true }, (token) => {
if (chrome.runtime.lastError) {
reject(chrome.runtime.lastError);
} else {
resolve(token);
}
});
});
}
// Use it to call Google APIs
const token = await getGoogleToken();
const response = await fetch('https://www.googleapis.com/calendar/v3/calendars/primary/events', {
headers: { 'Authorization': `Bearer ${token}` }
});
To use getAuthToken, you must register your extension as an OAuth client in Google Cloud Console and add the OAuth client ID to your manifest:
"oauth2": {
"client_id": "YOUR_CLIENT_ID.apps.googleusercontent.com",
"scopes": ["https://www.googleapis.com/auth/calendar.readonly"]
}
For non-Google OAuth providers, use launchWebAuthFlow which opens an OAuth URL and captures the redirect.
When to use it:
- Integrating with Google APIs (Calendar, Gmail, Drive, etc.)
- Any OAuth flow with proper redirect handling
- Avoiding the manual cookie/token management for auth
When not to use it:
- For simple API key auth (user provides their own key), don't use this — just store the key in
chrome.storage - For services that don't support OAuth, this won't help
chrome.history
Permission required: history
Available in: background, popup, options
Read and modify browsing history.
Common methods:
chrome.history.search({ text, startTime, endTime, maxResults }, callback)
chrome.history.getVisits({ url }, callback)
chrome.history.addUrl({ url })
chrome.history.deleteUrl({ url })
chrome.history.deleteRange({ startTime, endTime })
chrome.history.deleteAll()
Example — find all GitHub pages visited this week:
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;
const visits = await chrome.history.search({
text: 'github.com',
startTime: oneWeekAgo,
maxResults: 100
});
console.log(`Visited ${visits.length} GitHub pages this week`);
When to use it:
- Personal analytics tools
- Quick "find that page I visited yesterday" tools
- Privacy tools that clean specific history entries
When not to use it:
- High-trust permission. Users get a strong warning. Only request when genuinely needed.
chrome.bookmarks
Permission required: bookmarks
Available in: background, popup, options
Read and modify bookmarks.
Common methods:
chrome.bookmarks.getTree(callback)
chrome.bookmarks.search({ query }, callback)
chrome.bookmarks.create({ parentId, title, url })
chrome.bookmarks.update(id, { title, url })
chrome.bookmarks.move(id, { parentId, index })
chrome.bookmarks.remove(id)
chrome.bookmarks.removeTree(id)
chrome.bookmarks.onCreated.addListener((id, bookmark) => { ... })
chrome.bookmarks.onChanged.addListener((id, changeInfo) => { ... })
Example — bookmark organizer:
// Find all bookmarks tagged with 'reading'
const matches = await chrome.bookmarks.search({ query: 'reading' });
// Create a new folder and move them
const folder = await chrome.bookmarks.create({
parentId: '1', // Bookmarks bar
title: 'Reading List'
});
for (const bookmark of matches) {
if (bookmark.url) {
await chrome.bookmarks.move(bookmark.id, { parentId: folder.id });
}
}
When to use it:
- Bookmark manager extensions
- Read-it-later tools that integrate with native bookmarks
- Tagging or organization layers on top of bookmarks
chrome.downloads
Permission required: downloads
Available in: background, popup, options
Manage browser downloads — start, monitor, pause, resume, cancel.
Common methods:
chrome.downloads.download({ url, filename, saveAs }, downloadId => { ... })
chrome.downloads.search({ query }, callback)
chrome.downloads.pause(downloadId)
chrome.downloads.resume(downloadId)
chrome.downloads.cancel(downloadId)
chrome.downloads.onCreated.addListener(downloadItem => { ... })
chrome.downloads.onChanged.addListener(delta => { ... })
Example — bulk image downloader:
async function downloadAllImages(imageUrls) {
for (const [index, url] of imageUrls.entries()) {
await chrome.downloads.download({
url: url,
filename: `images/image-${index}.jpg`,
saveAs: false // Don't prompt for each
});
}
}
When to use it:
- Bulk download tools
- File-save automations
- Download progress monitors
chrome.offscreen
Permission required: offscreen
Available in: background
Create an offscreen document — a hidden HTML page with full DOM access, runnable from a service worker. Solves the problem that service workers don't have a DOM.
Common methods:
chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['DOM_PARSER'],
justification: 'Parse HTML to extract structured data'
})
chrome.offscreen.hasDocument() // Check if one exists
chrome.offscreen.closeDocument() // Tear down
Example — parse HTML in background:
// In background.js
async function parseHTML(html) {
// Create offscreen document if needed
if (!await chrome.offscreen.hasDocument()) {
await chrome.offscreen.createDocument({
url: 'offscreen.html',
reasons: ['DOM_PARSER'],
justification: 'Parse HTML to extract data'
});
}
// Send HTML to offscreen for parsing
return chrome.runtime.sendMessage({
target: 'offscreen',
type: 'PARSE_HTML',
html: html
});
}
// In offscreen.js (loaded by offscreen.html)
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.target === 'offscreen' && msg.type === 'PARSE_HTML') {
const parser = new DOMParser();
const doc = parser.parseFromString(msg.html, 'text/html');
const title = doc.querySelector('title')?.textContent;
sendResponse({ title });
}
});
Valid reasons for creating an offscreen document include AUDIO_PLAYBACK, DOM_PARSER, DOM_SCRAPING, CLIPBOARD, LOCAL_STORAGE, BLOBS, and several others. You must declare at least one accurate reason; Chrome may reject mismatched declarations.
When to use it:
- Service workers needing DOM access (HTML parsing, audio playback, clipboard ops)
- Running code that requires
documentorwindowfrom background context
When not to use it:
- If a content script in any tab can do the work, prefer that — content scripts already have DOM access.
Less common but worth knowing
chrome.permissions
Programmatically request optional permissions at runtime.
const granted = await chrome.permissions.request({
permissions: ['history'],
origins: ['https://*.example.com/*']
});
if (granted) {
// User accepted; use the permission
}
Best practice: request optional permissions only at the point the user triggers a feature that needs them. Asking up front feels intrusive.
chrome.i18n
Internationalization at runtime. Reads strings from _locales/[lang]/messages.json based on the user's Chrome language.
const greeting = chrome.i18n.getMessage('greeting', ['World']);
// Returns 'Hello, World!' if messages.json has:
// { "greeting": { "message": "Hello, $1$!" } }
chrome.tts
Text-to-speech synthesis. Useful for accessibility tools and reading apps.
chrome.tts.speak('Hello, world', {
voiceName: 'Google US English',
rate: 1.0,
onEvent: (event) => {
if (event.type === 'end') {
console.log('Done speaking');
}
}
});
chrome.sessions
Restore recently closed tabs and windows.
const recent = await chrome.sessions.getRecentlyClosed({ maxResults: 5 });
chrome.sessions.restore(recent[0].tab.sessionId);
chrome.management
Manage other installed extensions and themes. Powerful but heavily restricted; users see a strong permission warning.
const extensions = await chrome.management.getAll();
const disabled = extensions.filter(e => !e.enabled);
Used by extension manager extensions and a few security tools. Rarely needed otherwise.
chrome.windows vs chrome.tabs
A common confusion: when do you need windows vs tabs?
- A window is a Chrome window — the browser frame with tabs.
- A tab is one tab within a window.
Most extensions only care about tabs. Use chrome.windows only when you need to create separate window frames (popup windows, picture-in-picture) or manage window state (minimize, focus, size).
Cross-cutting patterns
A few patterns that span multiple APIs and are worth internalizing:
Top-level listener registration. Always register event listeners (onMessage, onAlarm, onCommand, etc.) at the top level of your background script, not inside other handlers. The reason: service workers can terminate and restart. When they restart, only top-level code runs immediately. Listeners registered inside functions won't be re-registered until that function runs again, missing events in the meantime.
// CORRECT: Top-level registration
chrome.runtime.onMessage.addListener(handleMessage);
chrome.alarms.onAlarm.addListener(handleAlarm);
chrome.commands.onCommand.addListener(handleCommand);
// WRONG: Inside another handler — doesn't survive service worker restart
chrome.runtime.onInstalled.addListener(() => {
chrome.runtime.onMessage.addListener(handleMessage); // This won't re-register on next start
});
Async sendResponse pattern. When responding asynchronously to messages, return true from the listener to keep the channel open.
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
doAsyncWork().then(result => sendResponse(result));
return true; // Required
});
Storage as state. Service workers can terminate, popups close, tabs reload — module-level variables can disappear unpredictably. Store anything that matters in chrome.storage. Read from storage on every event handler invocation rather than caching in memory.
Promise vs callback APIs. Chrome 88+ supports promise-style APIs for most chrome.* methods. Newer codebases use the promise form for clarity:
// Promise style (preferred)
const tabs = await chrome.tabs.query({ active: true });
// Callback style (still works)
chrome.tabs.query({ active: true }, (tabs) => { ... });
Error handling. Many Chrome APIs surface errors via chrome.runtime.lastError instead of throwing. Always check it after API calls in callback style; promise-style usage handles errors via try/catch normally.
chrome.tabs.create({ url }, (tab) => {
if (chrome.runtime.lastError) {
console.error('Tab creation failed:', chrome.runtime.lastError.message);
return;
}
// Tab created successfully
});
What this reference doesn't cover
Several APIs exist that this reference skips, either because they're deprecated, niche, or limited to specific platforms:
chrome.gcm— Google Cloud Messaging, mostly superseded by FCMchrome.platformKeys— Chrome OS only, smart card and certificate accesschrome.fileSystemProvider— Chrome OS only, custom file system providerschrome.printerProvider— Chrome OS only, custom print driverschrome.documentScan— Chrome OS only, document scanningchrome.enterprise.*— managed-device-only APIschrome.devtools.*— DevTools panel extensions (different category from regular extensions)
If your extension specifically needs any of these, Chrome's official docs at developer.chrome.com are the authoritative reference. The Manifest V3 docs there are usually accurate; the API docs are sometimes out of date.
Next steps
For the manifest declarations that grant access to these APIs, see the Manifest V3 reference.
For the broader picture of how Chrome extensions are built, packaged, and shipped, the complete guide is the structured walkthrough.
And if writing all this Chrome API code by hand is more work than the extension's value justifies, PlugThis generates working Chrome extension code from plain-English descriptions. Every chrome.* API gets called in the right context, with correct error handling, and the manifest permissions match what the code actually uses. No more debugging which context can call which API.