Chrome’s built-in tab groups are manual, and grouping by domain isn’t useful either. I have GitHub, a pull request, and a CI log open for one task, and a calendar, email, and chat open for another. The domain doesn’t tell you the topic. What I want is a list organized by what each tab is for.

Tab Manager is a small extension that does that. It’s about 850 lines of plain JavaScript, HTML, and CSS with no build step.

How the Grouping Works

The core is a list of rules in grouper.js. Each rule has a name, a color, and a test function that sees the tab’s URL and title. The first rule that matches wins:

  • Local Dev for localhost and loopback addresses
  • Code for GitHub, GitLab, and Bitbucket, plus titles that mention pull requests or commits
  • Email, Calendar, Chat by service domain
  • Docs & Writing, Reference & Docs, AI, Shopping, Media, Social, News, Finance

Order matters because rules overlap. A GitHub tab titled “API reference” could be Code or Reference, and putting the more specific rule first decides it. Matching on the title as well as the URL catches things like a pull request page on a self-hosted instance that no domain list would know about.

Anything that matches nothing falls back to its domain, so unclear tabs still group sensibly instead of landing in a pile called “Other.” Known topics sort first and fallback domains after.

The Window

Click the toolbar icon and a window opens at 380 by 800 with a search box and a list of groups. A group header collapses and expands. A tab row switches to that tab and focuses its window, and has a close button. There are Auto-group, Collapse All, and Expand All buttons, plus a rename dialog for groups.

The list updates as tabs are created, closed, moved, activated, or change title.

You Can Overrule It

Automatic grouping is wrong sometimes, so there are three overrides, all saved in chrome.storage.local:

  • Drag a tab onto another group to assign it manually. Manual assignments win over the rules.
  • Rename a group to whatever you like.
  • Collapsed state persists, so the list looks the same when you reopen it.

What Broke

All four fixes landed on the same day as the initial commit, which is a decent summary of Manifest V3 extension work.

  1. The invalid permission. My first manifest asked for a favicon permission that doesn’t exist, and Chrome refused to load the extension. Favicons are available without it.
  2. Crashing the extensions page. The background worker relayed every tab event to the panel with chrome.runtime.sendMessage, and the extension was crashing the chrome://extensions page, probably in a crash loop. I stripped the worker down to opening the panel and moved the tab listeners into the panel itself, which can call the Chrome APIs directly. The commit says “likely” because I never pinned down the exact cause. I also dropped the suggested keyboard shortcut from the manifest to avoid conflicts.
  3. Malformed icons and an inline handler. The generated PNG icons were invalid, and one inline event handler violated the extension’s content security policy. I regenerated the icons and moved the handler into the script file.
  4. Side panel to popup. I started with the Chrome side panel API, since a persistent panel was the original idea. It was fussy, and a floating popup window turned out to be simpler and more reliable. The service worker remembers the window ID, focuses the existing window if you click again instead of opening a second, and clears the ID when the window closes.

The popup loses the always-docked feel, but it’s a normal window, so it can sit on a second monitor.

What I’d Do Next

  • Let the rule list be edited from the UI instead of in code
  • Use tab groups natively, so Chrome’s own strip reflects the topic grouping
  • Handle multiple windows instead of just the current one

For now it does the one thing I wanted, and it’s small enough to read in an afternoon.