Sebastian Gomez
Exploring Tabs in Chrome Extensions
In this post we'll dig deeper into working with tabs in Chrome extensions. Earlier in this series we covered basic functions like chrome.runtime.onInstalled and chrome.tabs.create. Now we'll focus on the various capabilities the Chrome Tabs API offers and how we can use them to improve our extensions.
A note on Manifest V3. Every example in this chapter targets Manifest V3, which is the current standard for Chrome extensions (Manifest V2 has reached the end of its life). Two practical consequences to keep in mind: background code no longer lives in a persistent background page but in a service worker, so event listeners must be registered at the top level of that service worker (not inside a function that runs later); and since Chrome 88 every chrome.tabs method returns a Promise, so we can use async/await, which is the idiomatic style today. You can check the official reference at developer.chrome.com/docs/extensions/reference/api/tabs.
Querying tabs with the Chrome Tabs API
The Chrome Tabs API lets you perform several operations such as querying, creating, updating, and removing tabs. One of the most used functions is chrome.tabs.query, which lets us get information about the active tabs in the current window.
Using `chrome.tabs.query`: this function takes a queryInfo object with the query filters. In Manifest V3 the idiomatic approach is to use the version that returns a Promise and await it.
// Modern style (MV3) with async/await
async function logActiveTab() {
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
console.log(tab.id); // ID of the active tab
}
logActiveTab();If you work with legacy code, you may see the same query written with a callback. It still works, but today we prefer async/await:
// Legacy callback style (still valid)
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
console.log(tabs[0].id); // ID of the active tab
});Getting tab information: you can read various tab details, such as the ID, status, URL, title, and more.
async function logTabInfo() {
const [activeTab] = await chrome.tabs.query({ active: true, currentWindow: true });
console.log(`ID: ${activeTab.id}, URL: ${activeTab.url}`);
}
logTabInfo();Important: sensitive data requires permissions. Properties like tab.url, tab.title, and tab.favIconUrl are only available if your extension declares the tabs permission or has host permissions that match the page. Without those permissions, these properties come back as undefined, even though the rest of the object (for example tab.id) is present. Remember to declare it in your manifest.json, for example with "permissions": ["tabs"].
Events in the Chrome Tabs API
The Chrome Tabs API provides several events that let us react to changes in tabs. Remember to register these listeners at the top level of the service worker, so they keep working even after the service worker has been suspended and restarted. Let's look at some of the most common events.
`onCreated` event: fires when a new tab is created.
chrome.tabs.onCreated.addListener((tab) => {
console.log('Tab created:', tab);
});`onUpdated` event: fires when a tab is updated, for example when the URL changes or it finishes loading.
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
console.log('Tab updated:', tab);
});`onActivated` event: fires when a tab becomes active.
chrome.tabs.onActivated.addListener((activeInfo) => {
console.log('Tab activated:', activeInfo.tabId);
});A worked example with events
Suppose we want to log every time a tab is created or updated and show that information in the console. Notice how, inside onActivated, we now use await chrome.tabs.get(...) instead of a nested callback.
chrome.tabs.onCreated.addListener((tab) => {
console.log('Tab created:', tab);
});
chrome.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.status === 'complete') {
console.log('Tab finished loading:', tab);
}
});
chrome.tabs.onActivated.addListener(async (activeInfo) => {
const tab = await chrome.tabs.get(activeInfo.tabId);
console.log('Tab activated:', tab);
});Managing tabs
Beyond querying and listening for events, you can also perform actions such as moving or closing tabs.
Moving tabs: use chrome.tabs.move to change a tab's position.
async function moveTab(tabId, newIndex) {
const tab = await chrome.tabs.move(tabId, { index: newIndex });
console.log('Tab moved:', tab);
}Closing tabs: use chrome.tabs.remove to close one or more tabs.
async function closeTab(tabId) {
await chrome.tabs.remove(tabId);
console.log('Tab closed');
}Conclusion
The Chrome Tabs API is a powerful tool for building extensions that interact with browser tabs in advanced ways. From querying and managing tabs to reacting to specific events, this API offers everything you need to create rich, functional user experiences, and with Manifest V3 and Promises the code ends up cleaner and easier to read.
Suggested exercises
- Create a minimal Manifest V3 extension with a service worker and register the
onCreated,onUpdated, andonActivatedlisteners at the top level. Watch them fire in the service worker console. - Declare the
tabspermission in yourmanifest.jsonand verify thattab.urlandtab.titleno longer come back asundefined. Then remove the permission and observe the difference. - Rewrite a callback example in the
async/awaitstyle usingchrome.tabs.query, and add error handling withtry/catch.
3-point summary
- These examples target Manifest V3: background code lives in a service worker and listeners are registered at its top level.
- Since Chrome 88 every
chrome.tabsmethod returns a Promise, so the idiomatic style isasync/await, although the callback form is still valid. - To read
tab.url,tab.title, ortab.favIconUrlyou need thetabspermission or host permissions; otherwise those properties come back asundefined.
I hope this post helped you better understand how to work with tabs in Chrome and inspires you to explore the possibilities the browser APIs offer for building more dynamic and useful extensions.
Leave me a comment if it helped, if you want to add an opinion, or if you have any questions. And remember, if you liked it, you can also share it using the social links below. See you next time!
Sebastian Gomez
Creador de contenido principalmente acerca de tecnología.