Chrome Tabs API Complete Reference
18 min readChrome Tabs API Reference
The chrome.tabs API lets you create, modify, query, and rearrange tabs in the browser. It is one of the most heavily used Chrome extension APIs.
Permissions
Most chrome.tabs methods work without the tabs permission. The permission only controls access to sensitive properties (url, title, favIconUrl, pendingUrl) on Tab objects.
{
"permissions": ["tabs"]
}
See the tabs permission reference for a detailed breakdown of what requires permission and what does not.
Tab Object
Every method that returns tab data provides a chrome.tabs.Tab object:
| Property | Type | Needs tabs Permission |
|---|---|---|
id |
number |
No |
index |
number |
No |
windowId |
number |
No |
active |
boolean |
No |
pinned |
boolean |
No |
highlighted |
boolean |
No |
incognito |
boolean |
No |
status |
TabStatus \| undefined |
No |
discarded |
boolean |
No |
autoDiscardable |
boolean |
No |
groupId |
number |
No |
url |
string \| undefined |
Yes |
pendingUrl |
string \| undefined |
Yes |
title |
string \| undefined |
Yes |
favIconUrl |
string \| undefined |
Yes |
openerTabId |
number \| undefined |
No |
audible |
boolean \| undefined |
No |
mutedInfo |
MutedInfo \| undefined |
No |
width |
number \| undefined |
No |
height |
number \| undefined |
No |
sessionId |
string \| undefined |
No |
lastAccessed |
number |
No |
Core Methods
chrome.tabs.query(queryInfo)
Find tabs matching a filter. The most-used method in the API.
// Get the active tab in the current window
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
// Get all tabs in all windows
const allTabs = await chrome.tabs.query({});
// Tabs matching a URL pattern (requires tabs or host permission)
const gmailTabs = await chrome.tabs.query({ url: "https://mail.google.com/*" });
// All pinned tabs
const pinned = await chrome.tabs.query({ pinned: true });
// Tabs with audible media
const audible = await chrome.tabs.query({ audible: true });
// Combine filters: active, non-incognito, in current window
const tabs = await chrome.tabs.query({
active: true,
currentWindow: true,
incognito: false,
});
QueryInfo properties: active, pinned, audible, muted, highlighted, discarded, autoDiscardable, currentWindow, lastFocusedWindow, status, title, url (glob pattern), windowId, windowType, groupId, index.
chrome.tabs.create(createProperties)
Open a new tab.
// Simple: open a URL
const tab = await chrome.tabs.create({ url: "https://example.com" });
// Open next to current tab
const [current] = await chrome.tabs.query({ active: true, currentWindow: true });
const tab = await chrome.tabs.create({
url: "https://example.com",
index: current.index + 1,
active: false, // open in background
});
// Open extension page
const tab = await chrome.tabs.create({
url: chrome.runtime.getURL("options.html"),
});
CreateProperties: url, windowId, index, active, pinned, openerTabId.
chrome.tabs.update(tabId, updateProperties)
Modify an existing tab.
// Navigate a tab to a new URL
await chrome.tabs.update(tabId, { url: "https://example.com" });
// Pin the active tab
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
await chrome.tabs.update(tab.id!, { pinned: true });
// Mute a tab
await chrome.tabs.update(tabId, { muted: true });
// Activate (focus) a specific tab
await chrome.tabs.update(tabId, { active: true });
UpdateProperties: url, active, highlighted, pinned, muted, openerTabId, autoDiscardable.
chrome.tabs.remove(tabIds)
Close one or more tabs.
// Close a single tab
await chrome.tabs.remove(tabId);
// Close multiple tabs
await chrome.tabs.remove([tabId1, tabId2, tabId3]);
// Close all tabs matching a pattern
const tabs = await chrome.tabs.query({ url: "https://example.com/*" });
await chrome.tabs.remove(tabs.map((t) => t.id!));
chrome.tabs.reload(tabId, reloadProperties?)
Refresh a tab.
// Soft reload
await chrome.tabs.reload(tabId);
// Hard reload (bypass cache)
await chrome.tabs.reload(tabId, { bypassCache: true });
chrome.tabs.move(tabIds, moveProperties)
Reorder tabs within or across windows.
// Move tab to position 0
await chrome.tabs.move(tabId, { index: 0 });
// Move tab to another window
await chrome.tabs.move(tabId, { windowId: targetWindowId, index: -1 });
// Move multiple tabs together
await chrome.tabs.move([tabId1, tabId2], { index: 0 });
chrome.tabs.group(options) / chrome.tabs.ungroup(tabIds)
Manage tab groups (Chrome 88+).
// Group tabs together
const groupId = await chrome.tabs.group({ tabIds: [tabId1, tabId2] });
// Customize the group
await chrome.tabGroups.update(groupId, {
title: "Research",
color: "blue",
collapsed: false,
});
// Add a tab to an existing group
await chrome.tabs.group({ tabIds: tabId3, groupId });
// Ungroup tabs
await chrome.tabs.ungroup([tabId1, tabId2]);
chrome.tabs.discard(tabId?)
Free memory by discarding a tab. The tab remains visible in the tab strip but its content is unloaded.
await chrome.tabs.discard(tabId);
// Tab will reload when the user clicks on it
chrome.tabs.captureVisibleTab(windowId?, options?)
Take a screenshot of the visible area of the active tab. Requires <all_urls> or activeTab permission.
const dataUrl = await chrome.tabs.captureVisibleTab(undefined, {
format: "png",
quality: 100,
});
// dataUrl is a base64-encoded image
chrome.tabs.sendMessage(tabId, message, options?)
Send a message to content scripts running in a specific tab.
// Send to all frames in a tab
const response = await chrome.tabs.sendMessage(tabId, {
type: "getData",
selector: ".price",
});
// Send to a specific frame
const response = await chrome.tabs.sendMessage(
tabId,
{ type: "getData" },
{ frameId: 0 }, // main frame
);
chrome.tabs.goBack(tabId?) / chrome.tabs.goForward(tabId?)
Navigate tab history.
await chrome.tabs.goBack(tabId);
await chrome.tabs.goForward(tabId);
chrome.tabs.getZoom(tabId?) / chrome.tabs.setZoom(tabId?, zoomFactor)
const zoom = await chrome.tabs.getZoom(tabId); // e.g. 1.0
await chrome.tabs.setZoom(tabId, 1.5); // 150%
await chrome.tabs.setZoom(tabId, 0); // reset to default
chrome.tabs.detectLanguage(tabId?)
const lang = await chrome.tabs.detectLanguage(tabId);
console.log(lang); // "en", "fr", "ja", etc.
Events
chrome.tabs.onCreated
Fires when a new tab is created.
chrome.tabs.onCreated.addListener((tab) => {
console.log("New tab:", tab.id, tab.pendingUrl);
});
chrome.tabs.onUpdated
Fires when a tab’s properties change (URL, title, loading status, etc.). This is the most frequently fired tab event.
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
// changeInfo contains only the properties that changed
if (changeInfo.status === "complete") {
console.log("Tab finished loading:", tab.url);
}
if (changeInfo.title) {
console.log("Title changed to:", changeInfo.title);
}
});
Filter for performance — avoid running logic on every update:
chrome.tabs.onUpdated.addListener(
(tabId, changeInfo, tab) => {
// Only fires for matching conditions
console.log("Page loaded:", tab.url);
},
{ properties: ["status"], urls: ["https://example.com/*"] },
);
chrome.tabs.onRemoved
chrome.tabs.onRemoved.addListener((tabId, removeInfo) => {
console.log("Tab closed:", tabId);
console.log("Window closing?", removeInfo.isWindowClosing);
});
chrome.tabs.onActivated
Fires when the active tab in a window changes.
chrome.tabs.onActivated.addListener((activeInfo) => {
console.log("Active tab:", activeInfo.tabId, "in window:", activeInfo.windowId);
});
chrome.tabs.onMoved / onAttached / onDetached
// Tab moved within a window
chrome.tabs.onMoved.addListener((tabId, moveInfo) => {
console.log(`Tab ${tabId} moved from ${moveInfo.fromIndex} to ${moveInfo.toIndex}`);
});
// Tab moved to a different window
chrome.tabs.onDetached.addListener((tabId, detachInfo) => {
console.log(`Tab ${tabId} detached from window ${detachInfo.oldWindowId} at position ${detachInfo.oldPosition}`);
});
chrome.tabs.onAttached.addListener((tabId, attachInfo) => {
console.log(`Tab ${tabId} attached to window ${attachInfo.newWindowId} at position ${attachInfo.newPosition}`);
});
chrome.tabs.onReplaced
Fires when a tab is replaced by another (e.g. prerendering).
chrome.tabs.onReplaced.addListener((addedTabId, removedTabId) => {
console.log(`Tab ${removedTabId} replaced by ${addedTabId}`);
});
Using with @theluckystrike/webext-messaging
Build a typed tab manager with the background querying tabs and sending results to the popup:
// shared/messages.ts
type Messages = {
queryTabs: {
request: { query: chrome.tabs.QueryInfo };
response: Array<{ id: number; title: string; url: string; active: boolean }>;
};
closeTab: {
request: { tabId: number };
response: { success: boolean };
};
moveTab: {
request: { tabId: number; index: number };
response: { success: boolean };
};
};
// background.ts
import { createMessenger } from "@theluckystrike/webext-messaging";
const msg = createMessenger<Messages>();
msg.onMessage({
queryTabs: async ({ query }) => {
const tabs = await chrome.tabs.query(query);
return tabs.map((t) => ({
id: t.id!,
title: t.title || "Untitled",
url: t.url || "",
active: t.active,
}));
},
closeTab: async ({ tabId }) => {
await chrome.tabs.remove(tabId);
return { success: true };
},
moveTab: async ({ tabId, index }) => {
await chrome.tabs.move(tabId, { index });
return { success: true };
},
});
// popup.ts
const msg = createMessenger<Messages>();
const tabs = await msg.send("queryTabs", { query: { currentWindow: true } });
Using with @theluckystrike/webext-storage
Track tab activity and persist it:
import { defineSchema, createStorage } from "@theluckystrike/webext-storage";
const schema = defineSchema({
tabHistory: [] as Array<{ url: string; title: string; visitedAt: number }>,
tabStats: { totalOpened: 0, totalClosed: 0 },
});
const storage = createStorage({ schema, area: "local" });
chrome.tabs.onCreated.addListener(async () => {
const stats = await storage.get("tabStats");
await storage.set("tabStats", { ...stats, totalOpened: stats.totalOpened + 1 });
});
chrome.tabs.onRemoved.addListener(async () => {
const stats = await storage.get("tabStats");
await storage.set("tabStats", { ...stats, totalClosed: stats.totalClosed + 1 });
});
chrome.tabs.onUpdated.addListener(async (tabId, changeInfo, tab) => {
if (changeInfo.status === "complete" && tab.url) {
const history = await storage.get("tabHistory");
history.unshift({ url: tab.url, title: tab.title || "", visitedAt: Date.now() });
await storage.set("tabHistory", history.slice(0, 100));
}
});
Common Patterns
Get the current tab from a popup or action click
// From popup.ts or action click handler
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
Inject a content script into a specific tab
// Requires "scripting" permission
await chrome.scripting.executeScript({
target: { tabId: tab.id! },
files: ["content.js"],
});
Duplicate a tab
const newTab = await chrome.tabs.duplicate(tabId);
Wait for a tab to finish loading
function waitForTabLoad(tabId: number): Promise<chrome.tabs.Tab> {
return new Promise((resolve) => {
function listener(id: number, changeInfo: chrome.tabs.TabChangeInfo, tab: chrome.tabs.Tab) {
if (id === tabId && changeInfo.status === "complete") {
chrome.tabs.onUpdated.removeListener(listener);
resolve(tab);
}
}
chrome.tabs.onUpdated.addListener(listener);
});
}
Gotchas
-
tab.idcan be undefined in rare cases (e.g. devtools tabs). Always use non-null assertion (tab.id!) only when you’re certain the tab has an ID. -
onUpdatedfires multiple times per navigation — once forstatus: "loading"and again forstatus: "complete", plus for title/favicon changes. Always filter onchangeInfo. -
URL matching in
query()uses glob patterns, not regex. Use*for wildcards:"https://example.com/*". -
captureVisibleTabcaptures what’s visible, not the full page. For full-page screenshots, you need a content script. -
tabs.sendMessagethrows an error if no content script is listening in the target tab (“Could not establish connection. Receiving end does not exist.”). Always wrap in try/catch. -
Tab IDs are not stable across sessions. Never persist tab IDs — they change on browser restart.
Related
- tabs permission
- activeTab permission
- Windows API
- Chrome tabs API docs
Frequently Asked Questions
How do I get the current tab?
Use chrome.tabs.query() with {active: true, currentWindow: true} to get the active tab in the current window.
Can I create new tabs programmatically?
Yes, use chrome.tabs.create() with a URL parameter to open new tabs.
Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.