tabs Permission Reference
9 min readtabs Permission Reference
What It Does
- Grants persistent access to tab metadata on all tabs in the browser
- Access to
url,pendingUrl,title,favIconUrlon Tab objects - Allows querying and monitoring tabs across all windows
What You Get WITHOUT tabs
Without the tabs permission, you still get access to these Tab properties:
| Property | Type | Description |
|---|---|---|
tab.id |
number \| undefined |
Unique tab identifier |
tab.index |
number |
Position in tab strip (0-based) |
tab.windowId |
number |
ID of the parent window |
tab.active |
boolean |
Whether tab is active in its window |
tab.pinned |
boolean |
Whether tab is pinned |
tab.status |
"loading" \| "complete" |
Loading state |
tab.incognito |
boolean |
Whether in incognito mode |
You also get access to all chrome.tabs methods:
chrome.tabs.query()— find tabs by various criteriachrome.tabs.create()— open new tabschrome.tabs.update()— modify tab propertieschrome.tabs.remove()— close tabschrome.tabs.reload()— refresh tabschrome.tabs.goBack()/chrome.tabs.goForward()— navigationchrome.tabs.highlight()— bring tabs to frontchrome.tabs.move()— reorder tabschrome.tabs.captureVisibleTab()— screenshots (with host permission)chrome.tabs.detectLanguage()— detect page languagechrome.tabs.getZoom()/chrome.tabs.setZoom()— zoom controlchrome.tabs.discard()— discard tabs to save memory
What You Need tabs FOR
The tabs permission is required to access these sensitive Tab properties:
| Property | Type | Description |
|---|---|---|
tab.url |
string \| undefined |
Full URL of the tab |
tab.title |
string \| undefined |
Page title |
tab.favIconUrl |
string \| undefined |
Favicon URL |
tab.pendingUrl |
string \| undefined |
URL being navigated to |
Without tabs permission, these properties will be undefined when you query tabs.
Manifest Configuration
Required permission
{
"permissions": ["tabs"]
}
Optional permission (recommended for more flexible UX)
{
"optional_permissions": ["tabs"]
}
With optional permissions, users can install without granting tab access, and you can request it later:
// Request optional tabs permission
const granted = await chrome.permissions.request({ permissions: ["tabs"] });
Using with @theluckystrike/webext-permissions
Checking if granted
import { checkPermission, requestPermission } from "@theluckystrike/webext-permissions";
const result = await checkPermission("tabs");
console.log(result);
// { permission: "tabs", granted: boolean, description: "Read information about open tabs" }
if (!result.granted) {
const requested = await requestPermission("tabs");
console.log(requested); // true if user granted
}
Description from PERMISSION_DESCRIPTIONS
import { PERMISSION_DESCRIPTIONS } from "@theluckystrike/webext-permissions";
PERMISSION_DESCRIPTIONS.tabs;
// "Read information about open tabs"
Using with @theluckystrike/webext-messaging
Common pattern: background script queries tabs, sends data to popup/panel.
// shared/messages.ts
type Messages = {
getAllTabs: {
request: { windowId?: number };
response: Array<{ id: number; title: string; url: string; active: boolean }>;
};
getActiveTab: {
request: void;
response: { id: number; title: string; url: string; favIconUrl?: string };
};
};
// background.ts
import { createMessenger } from "@theluckystrike/webext-messaging";
const msg = createMessenger<Messages>();
msg.onMessage({
getAllTabs: async (payload) => {
const tabs = await chrome.tabs.query(payload.request?.windowId ? { windowId: payload.request.windowId } : {});
return tabs.map((tab) => ({
id: tab.id!,
title: tab.title || "Untitled",
url: tab.url || "",
active: tab.active,
}));
},
});
msg.onMessage({
getActiveTab: async () => {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
return {
id: tab.id!,
title: tab.title || "Untitled",
url: tab.url || "",
favIconUrl: tab.favIconUrl,
};
},
});
// popup.ts
const msg = createMessenger<Messages>();
const allTabs = await msg.send("getAllTabs", {});
const activeTab = await msg.send("getActiveTab", undefined);
console.log(`Found ${allTabs.length} tabs. Active: ${activeTab.title}`);
Using with @theluckystrike/webext-storage
Store tab-related preferences and recent tab data:
import { defineSchema, createStorage } from "@theluckystrike/webext-storage";
const schema = defineSchema({
lastVisitedTabs: [] as Array<{ id: number; title: string; url: string; timestamp: number }>,
pinnedTabsConfig: {
enabled: true,
maxPinned: 5,
},
tabGroups: {} as Record<string, number[]>, // groupName -> tabIds
});
const storage = createStorage({ schema });
// Store recent tab visit
async function trackTabVisit(tab: chrome.tabs.Tab) {
const recent = await storage.get("lastVisitedTabs");
const newEntry = {
id: tab.id!,
title: tab.title || "Untitled",
url: tab.url || "",
timestamp: Date.now(),
};
// Keep only last 20 tabs
const updated = [newEntry, ...recent].slice(0, 20);
await storage.set("lastVisitedTabs", updated);
}
// Save a tab to favorites
async function pinTabToGroup(groupName: string, tabId: number) {
const groups = await storage.get("tabGroups");
const current = groups[groupName] || [];
if (!current.includes(tabId)) {
groups[groupName] = [...current, tabId];
await storage.set("tabGroups", groups);
}
}
tabs vs activeTab Comparison
| Feature | tabs |
activeTab |
|---|---|---|
| Scope | All tabs, all windows | Only the active tab on user gesture |
| Duration | Persistent (always granted) | Temporary (expires on navigation) |
| URL access | All tabs | Only current active tab |
| User gesture required | No | Yes |
| Store warning | “Read your browsing history” | No warning |
| Chrome Web Store review | More scrutiny | Faster approval |
When to Use Which
Use tabs when:
- You need to read tab URLs/titles in the background
- You want to list all open tabs in a popup
- You need persistent access without user interaction
- You’re building a tab manager, history tool, or session manager
Use activeTab when:
- You only need to act on the current page
- You want to avoid scary permission warnings
- User trust/privacy is a priority
- You only act when user explicitly invokes your extension
Gotchas
-
Undefined without permission: Always check if
tab.url,tab.title,tab.favIconUrlexist before using them. They will beundefinediftabspermission isn’t granted. -
“Browsing history” warning: The
tabspermission triggers the “Read your browsing history” warning in the Chrome Web Store. This can reduce conversion rates. -
sendMessage works without tabs: You can use
chrome.tabs.sendMessage()to communicate with content scripts without thetabspermission — as long as you have host permissions for the target URL. -
Incognito tabs: In incognito windows,
tab.urlmay beundefinedeven withtabspermission unless you’ve declared"incognito": "spallow"or"incognito": "split"in your manifest. -
tab.id is always available: Even without
tabspermission,tab.idis always present — but you can’t read the URL/title associated with it. -
Extension pages: Tabs hosting extension pages (
chrome-extension://...) always expose their URL regardless of permissions.
Related Permissions
- activeTab — temporary access to active tab only
- scripting — inject content scripts (needs host permissions without tabs)
- storage — persist data
API Reference
Frequently Asked Questions
How do I get the current tab in Chrome extension?
Use chrome.tabs.query({active: true, currentWindow: true}) to get the currently active tab, or use the tab parameter in event listeners.
Can I access tab URLs without host permissions?
With the tabs permission, you can access limited tab metadata including URL, title, and favIconUrl. —
Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.