Build a Tab Grouper Chrome Extension with Plasmo: A Step-by-Step Guide
Introduction
Chrome extensions are lightweight tools that enhance your browsing experience—from managing passwords to organizing tabs. While building one from scratch can involve complex setup, the Plasmo framework simplifies the process, letting you focus on core functionality. In this guide, you'll create a Tab Grouper extension that automatically groups tabs by domain using TypeScript, React, and Plasmo. No prior extension experience needed—just basic React and TypeScript knowledge.

What You Need
- Node.js (v16 or higher) and npm (or yarn/pnpm)
- A code editor (VS Code recommended)
- Google Chrome (latest version)
- Basic familiarity with React, TypeScript, and command line
- Plasmo CLI (installed globally via
npm install -g plasmo)
Step-by-Step Instructions
Step 1: Scaffold the Project
Open your terminal and run:
plasmo init tab-grouper
cd tab-grouper
This creates a new directory with TypeScript, React, and a basic manifest.json already configured. Plasmo reads your package.json and generates the manifest automatically—you never edit it directly. The project includes a popup.tsx file and a background folder where you'll add scripts.
Step 2: Understand the Extension Structure
Chrome extensions consist of:
- Manifest – the blueprint (auto‑generated by Plasmo)
- Background script – runs persistently (or in a service worker) to manage state and events
- Popup – the UI shown when the user clicks the extension icon
- Content scripts (optional) – run in the context of web pages
For the Tab Grouper, you'll use the background script to handle tab creation and grouping, and the popup to trigger the action.
Step 3: Build the Background Script
Inside src/background, create a file index.ts. This script listens for messages from the popup and performs the grouping logic. Add:
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.action === 'groupTabs') {
groupTabsByDomain();
}
});
async function groupTabsByDomain() {
const tabs = await chrome.tabs.query({ currentWindow: true });
const domainMap: Record = {};
for (const tab of tabs) {
if (!tab.url || !tab.id) continue;
const url = new URL(tab.url);
const domain = url.hostname;
if (!domainMap[domain]) domainMap[domain] = [];
domainMap[domain].push(tab.id);
}
for (const [domain, tabIds] of Object.entries(domainMap)) {
if (tabIds.length > 1) {
await chrome.tabs.group({ tabIds });
await chrome.tabGroups.update((await chrome.tabs.group({ tabIds })).groupId, {
title: domain,
color: 'blue'
});
}
}
}
This script queries all tabs in the current window, groups tabs sharing the same domain, and assigns a color. Note: in a real implementation you'd handle errors and color cycling.
Step 4: Create the Popup UI
Open src/popup.tsx and replace the default content with a simple button:
import { sendToBackground } from '@plasmohq/messaging';
export default function Popup() {
const handleGroup = async () => {
await sendToBackground({ name: 'groupTabs' });
};
return (
Tab Grouper
);
}
Plasmo's sendToBackground utility sends a message to the background script. Make sure the message name matches the listener in the background script.

Step 5: Test the Extension Locally
In your terminal, run:
plasmo dev
This starts a development server that rebuilds on changes. Open Chrome and go to chrome://extensions. Enable Developer mode, click Load unpacked, and select the build/chrome-mv3-dev folder (or whatever path Plasmo outputs). The extension should appear in your toolbar. Open several tabs from different sites, click the extension icon, then click Group Tabs – your tabs will be grouped by domain.
Step 6: Add Polish and Error Handling
To make the extension robust, consider:
- Cycling through colors for each group.
- Ungrouping previous groups before re‑grouping.
- Showing a loading state in the popup.
- Handling edge cases (e.g., chrome:// URLs).
For example, to cycle colors, define an array ['red','orange','yellow','green','blue','purple'] and increment an index per domain.
Step 7: Prepare for Publishing
When you're ready to publish:
- Update
package.jsonwith an extension name, description, and icons. - Run
plasmo buildto create a production build. - Zip the
build/chrome-mv3-prodfolder. - Upload to the Chrome Web Store Developer Dashboard (requires a one‑time $5 registration fee).
Tips for Success
- Use the Chrome DevTools for debugging: inspect the background service worker via
chrome://extensions→ service worker link. - Leverage Plasmo's built‑in messaging – it simplifies communication between popup, background, and content scripts.
- Keep permissions minimal – only request what you need (e.g.,
tabsandtabGroups). - Test with many tabs to ensure performance doesn't degrade.
- Refer to the official Plasmo docs for advanced features like storage, options pages, and content scripts.
With these steps, you've built a fully functional Chrome extension using Plasmo. Now you can extend it with additional features or create entirely new extensions!
Related Articles
- Copilot Studio's Leap to .NET 10: WebAssembly Performance Gains
- How to Choose a JavaScript Module System for Your Application Architecture
- Interop 2026: Driving Web Consistency Across Browsers for a Fifth Year
- Dreaming of CSS ::nth-letter: Why It Doesn't Exist and How to Fake It
- Upcoming Rust WebAssembly Changes: The End of --allow-undefined and What It Means for Your Projects
- 10 Critical Concerns About Google's Prompt API and Gemini Nano
- Mastering CSS contrast-color(): Your Guide to Automated Text Contrast
- How Microsoft Copilot Studio Accelerates with .NET 10 and WebAssembly