I built Bookmash as a web app that works directly with your existing browser bookmarks.
The browser stays the source of truth. There is no separate bookmark database to import everything into, so the extension is responsible for reading the native bookmark tree, reacting to changes, and keeping the web app in sync.
I started with Chrome.
When I decided to support Firefox too, I assumed the extension would mostly need a few compatibility changes and a different manifest.
Both browsers support the WebExtensions model. The bookmark APIs look very similar.
So how different could they be?
More different than I expected.
Same API, different assumptions
One of the first differences was the bookmark tree itself.
In the Chrome implementation, I could rely on Chrome's bookmark structure and known parent IDs in places.
That assumption didn't carry over cleanly to Firefox.
For Firefox I ended up explicitly finding the Bookmarks Toolbar in the tree:
function findToolbarFolder(rootNode) {
if (!rootNode || !rootNode.children) return null;
const toolbar = rootNode.children.find(
(child) =>
child.id === "toolbar_____" ||
child.title === "Bookmarks Toolbar"
);
if (toolbar) return toolbar;
if (rootNode.children.length > 1) return rootNode.children[1];
return rootNode.children[0];
}
I also needed a compatibility layer to translate a Chrome-style parent ID into the actual Firefox toolbar folder.
It is a small piece of code, but it changed one of my assumptions: a bookmark tree is not necessarily the same bookmark tree just because both browsers expose a bookmarks API.
The more interesting problem was synchronization
Bookmash needs to react when something changes in the browser.
Create a bookmark in Firefox or Chrome and the dashboard should know about it. Rename a folder and the dashboard should update. Move something and the new structure should appear without requiring a manual refresh.
On Chrome, I could react to bookmark events individually.
Firefox made me rethink that approach.
I ran into a race condition where an event could fire but a subsequent read of the bookmark tree didn't necessarily give me the state I expected yet, particularly around newly created empty folders.
So the Firefox implementation ended up using a different strategy.
Instead of treating each event as enough information to reconstruct the new state, bookmark changes trigger a fresh read of the tree and the extension pushes that fresh tree to the Bookmash tabs:
browserAPI.bookmarks.onCreated.addListener(() => {
sendFreshBookmarksToAllTabs();
});
browserAPI.bookmarks.onRemoved.addListener(() => {
sendFreshBookmarksToAllTabs();
});
browserAPI.bookmarks.onMoved.addListener(() => {
sendFreshBookmarksToAllTabs();
});
sendFreshBookmarksToAllTabs() calls bookmarks.getTree() and sends the resulting state back to the dashboard.
That turned out to be a much safer boundary: the event tells Bookmash that something changed; the browser tree tells Bookmash what the state actually is.
And some APIs simply aren't identical
There were smaller differences too.
For example, Chrome exposes a bookmark children reorder event that I could use. Firefox doesn't support the equivalent onChildrenReordered event, so that path simply can't be shared one-to-one between the two implementations.
Even the manifests reveal that "Manifest V3" doesn't mean identical configuration.
Chrome uses a service worker:
"background": {
"service_worker": "background.js"
}
while my Firefox version uses:
"background": {
"scripts": ["background.js"]
}
The Firefox extension also needs Gecko-specific configuration that obviously doesn't exist in the Chrome version.
None of these differences is huge by itself.
That was actually the interesting part.
Cross-browser APIs don't guarantee cross-browser behavior
I didn't come away thinking Firefox was harder to develop for, or that Chrome's implementation was better.
What surprised me was simply how much behavior I had assumed would be identical because the APIs looked identical.
Supporting Firefox wasn't a rewrite. A lot of the underlying logic could stay the same.
But it also wasn't the packaging exercise I expected.
And for an app like Bookmash, where the browser itself is deliberately the source of truth, those differences matter more. I can't hide inconsistencies behind a server-side copy of the user's bookmark library. The native browser state is the state.
The main lesson I'd take into another browser-extension project is:
"Cross-browser API" doesn't necessarily mean "cross-browser behavior."
The interfaces can look almost identical while the assumptions underneath them aren't.
I'm curious if other extension developers have run into similar Chrome/Firefox differences, especially with APIs that looked compatible at first.
Interesting approach. What was the hardest part to get right?
Really relatable. How much time do you put into this each week?
This is useful. How are you finding your first users so far?
Thanks for writing this up. Bookmarking it for later.
Solid lesson. Which channel has worked best for you so far?