“I just want my Chrome bookmarks to open in a new tab by default.”

Sounds like a trivial feature, right? As it turns out, implementing this required a deep dive into Chrome APIs, creative hacks, and a journey full of unexpected roadblocks.

In this post, I’ll walk you through how I developed the Chrome extension Open bookmarks in a new tab. What seemed like a two-line tweak — turned out to be a rather tricky challenge, that required a non-trivial and inventive solution.


🧩 The Problem: Why Isn’t This a Built-In Feature?

The goal was simple: prevent the current tab from being replaced when I click on a bookmark. I just wanted Chrome to open bookmarks in a new tab by default.

Searching for “open bookmarks in a new tab” revealed tons of questions on Reddit, SuperUser, and Google Help forums. Clearly, this is something many users want (1, 2, 3).

The usual answers are:

  • “Just Ctrl + Click!”
  • “Middle-click the bookmark!”

Sure, those work — if you remember. But if you forget, you lose your current page’s context — scroll position, form data, or even unsaved work. That’s frustrating.

Another workaround is to use javascript: URLs in your bookmarks:

https://dev.to → javascript:window.open("https://dev.to")

This opens the bookmark in a new tab, yes — but at a cost:

  • ❌ You lose the favicon (it defaults to the generic globe icon):
    Default favicon

  • javascript: links don’t run on internal pages like chrome://extensions (which is especially annoying if you’re developing Chrome extensions 🤪).

So I started digging into the Chrome Extensions API to find a better way.


🔍 First Attempt: Capture Bookmark Navigation

My initial idea:

  1. Detect when a tab navigates due to a bookmark click.
  2. Open that URL in a new tab.
  3. Use chrome.tabs.goBack() to return the current tab to its previous state.
chrome.webNavigation.onCommitted.addListener(details => {
  if (details.transitionType === "auto_bookmark") {
    chrome.tabs.goBack(details.tabId);
    chrome.tabs.create({ url: details.url });
  }
});

It kind of worked. But it had a major flaw — the current tab does reload, and any page state is lost. This meant scroll positions, form inputs, or any unsaved data could be wiped out. Not acceptable.

Tab reload after bookmark click


💡 The Breakthrough: Downloads Instead of Navigation

After some thinking, I had an insight.

What if bookmarks triggered a download instead of a navigation?

If the page initiated a download, the current tab wouldn't change. And Chrome Extensions can detect and cancel downloads — avoiding any visual download animation.

So I created a dummy file empty.zip and added it to the extension. Then I created a bookmark that pointed to that file inside the extension.

chrome.downloads.onCreated.addListener((downloadItem) => {
  // capture download of chrome-extension://xxxxx/empty.zip
  if (downloadItem.url === chrome.runtime.getURL('/empty.zip')) {
    chrome.downloads.cancel(downloadItem.id);
    // Now open a new tab with the original URL
  }
});

And it worked! 🎉

  • ✅ Current tab remained untouched.
  • ✅ The download was captured and instantly canceled.
  • ✅ No download animation in Chrome UI.

However, two issues emerged:

  1. The bookmark's favicon changed to the extension’s icon — back to the favicon problem again.
  2. I needed to know the original URL the user wanted to visit. But now all I had was /empty.zip.

So the question became: how can I preserve the original URL, trigger a dummy download, and still keep the favicon?


🕸️ Attempted Tricks: Redirects, Fragments, and More

My next idea: keep the original URL in the bookmark, but redirect it internally using chrome.declarativeNetRequest.

{
  "id": 1,
  "priority": 1,
  "condition": {
    "regexFilter": "????",
    "resourceTypes": ["main_frame"]
  },
  "action": {
    "type": "redirect",
    "redirect": {
      "extensionPath": "/empty.zip"
    }
  }
}

With this, when a user clicks a bookmark, the navigation would be silently redirected to empty.zip. The downloadItem object had both url and finalUrl, so I could recover the original URL to open in a new tab:

{
  "id": 563,
  "url": "https://dev.to/"
  "finalUrl": "chrome-extension://xxxxxx/empty.zip",
  // ...
}

But the problem? Every navigation to this URL downloads empty.zip. I had no way to tell which navigations came from bookmarks.

❌ Fragments Don’t Work

I tried appending a hash:

https://dev.to#newtab

But URL fragments don’t reach the network stack — so declarativeNetRequest ignores them. No help there.

❌ Custom Hostnames? Nope.

I considered using something like:

about:blank?https://dev.to

But again — doing this broke the favicon. Back to the default globe.

I was stuck. I needed a URL that would both initiate a ZIP file download and open in the browser to retrieve the correct favicon. It turned out to be a sort of Schrödinger's URL!

Schrödinger's URL


🧙 The Hack That Worked: Username in URL

Then I remembered something from the HTTP spec: URLs can contain a username/password for basic auth.

https://username:[email protected]

This pattern is rarely used today. Most servers ignore the credentials entirely.

What if I use the username field to mark the bookmark as special?

For example (password is omitted):

  • ✅ Chrome displays the original favicon.
  • ✅ Most websites ignore the newtab@ part.
  • ✅ The username field does get passed to declarativeNetRequest.

Bingo! This gave me a unique marker to trigger a redirect to empty.zip.


🏗️ Final Architecture

Here’s how the extension works end-to-end:

  1. Every bookmark URL is rewritten with a username newtab:

  2. Any request with username newtab gets redirected to empty.zip.

  3. When empty.zip download is triggered, the extension immediately cancels it and opens the original URL in a new tab.

  4. Result: The original tab stays untouched. A new tab opens with the target URL. And the favicon is preserved:

Opening bookmark in a new tab


💤 The Wakeup Problem

Everything worked beautifully in development. But when testing the production build, a new issue appeared.

After a period of browser inactivity, Chrome suspends background workers in extensions. So when the bookmark is clicked, the extension isn't awake in time to capture the download — and Chrome starts showing a download animation.

To fix this, I added a background alarm to wake the extension every 30 seconds:

chrome.alarms.create('keepAlive', { periodInMinutes: 0.5 });
chrome.alarms.onAlarm.addListener(() => {
  // no-op to keep the background alive
});

This keeps the downloads listener ready and prevents any flicker or download animation.


⚠️ Known Limitations

This hacky-yet-functional solution has some caveats:

  • Internal URLs don’t work. chrome:// or chrome-extension:// URLs can’t include a username. Demo code:
const url = new URL('chrome://extensions/');
  url.username = 'newtab';
  console.log(url.href); // → 'chrome://extensions/'

So bookmarks pointing to internal pages still open in the same tab. However, you can still open them in a new tab by Ctrl + Click.


🎉 Conclusion

What started as a “5-minute side project” turned into a deep exploration of browser behavior, HTTP spec quirks, and extension APIs.

The final solution may look over-engineered — and it kind of is — but it works reliably, preserves user state, and doesn't break UX.

You can try the extension yourself: Open Bookmarks in a New Tab

And if you’re building Chrome extensions yourself, hopefully this journey helps you think outside the box (or at least inside the username part of the URL 😉).