Skip to content

Browser Extension

The BitMarks browser extension syncs your bookmarks across devices with end-to-end encryption.

Coming Soon

The extension will be available on the Chrome Web Store.

  1. Terminal window
    git clone https://github.com/bitmarks-sh/bitmarks.git
    cd bitmarks
  2. Terminal window
    bun install
  3. Terminal window
    cd packages/webextension
    bun run build
    1. Open chrome://extensions
    2. Enable “Developer mode”
    3. Click “Load unpacked”
    4. Select the packages/webextension/dist folder

  • Automatic sync: Changes sync in real-time across devices
  • Offline support: Changes queue when offline, sync when online
  • Conflict resolution: Last-Write-Wins with device ID tiebreaker
SourceDescription
BookmarksBrowser bookmarks from the bookmarks bar and folders
HistoryBrowsing history (optional)
Tab GroupsChrome tab groups (optional)
GitHub StarsYour starred repositories (optional)
  • Table View: Spreadsheet-like interface with sortable columns
  • Cards View: Visual card grid layout
  • Finder View: macOS Finder-style hierarchical browser
  • Reader View: Distraction-free reading mode

Access settings via the extension popup or chrome://extensions:

interface ExtensionSettings {
// Sync settings
syncEnabled: boolean;
autoSync: boolean;
syncInterval: number; // milliseconds
// Data sources
sources: {
bookmarks: boolean;
history: boolean;
tabGroups: boolean;
githubStars: boolean;
};
// View settings
defaultView: 'table' | 'cards' | 'finder' | 'reader';
theme: 'light' | 'dark' | 'system';
// GitHub integration
githubToken?: string;
}
const defaultSettings: ExtensionSettings = {
syncEnabled: true,
autoSync: true,
syncInterval: 300000, // 5 minutes
sources: {
bookmarks: true,
history: false,
tabGroups: false,
githubStars: false
},
defaultView: 'table',
theme: 'system'
};

packages/webextension/
├── src/
│ ├── background.ts # Service worker
│ ├── main.tsx # Popup entry
│ ├── options.tsx # Options page
│ ├── components/ # React components
│ │ ├── views/ # View mode components
│ │ └── ...
│ ├── contexts/ # React contexts
│ ├── hooks/ # Custom hooks
│ ├── lib/ # Utilities
│ │ ├── sync-manager.ts
│ │ ├── storage.ts
│ │ └── ...
│ └── providers/ # Data providers
│ ├── ChromeAPIProvider.ts
│ ├── ChromeHistoryProvider.ts
│ ├── ChromeTabGroupsProvider.ts
│ └── GitHubStarsProvider.ts
├── public/
│ └── manifest.json
└── package.json

The background script handles:

  • Bookmark change listeners
  • Real-time WebSocket sync
  • Periodic sync polling
  • Message passing with popup
background.ts
import { SyncManager } from './lib/sync-manager';
const syncManager = new SyncManager();
// Listen for bookmark changes
chrome.bookmarks.onCreated.addListener(async (id, bookmark) => {
await syncManager.pushChange('bookmark', id, 'create', bookmark);
});
chrome.bookmarks.onChanged.addListener(async (id, changes) => {
await syncManager.pushChange('bookmark', id, 'update', changes);
});
chrome.bookmarks.onRemoved.addListener(async (id) => {
await syncManager.pushChange('bookmark', id, 'delete');
});

Each data source has a provider:

providers/types.ts
interface DataProvider<T> {
name: string;
fetch(): Promise<T[]>;
subscribe?(callback: (changes: T[]) => void): () => void;
}
providers/ChromeAPIProvider.ts
export class ChromeAPIProvider implements DataProvider<Bookmark> {
name = 'bookmarks';
async fetch(): Promise<Bookmark[]> {
const tree = await chrome.bookmarks.getTree();
return this.flattenTree(tree);
}
subscribe(callback: (changes: Bookmark[]) => void) {
const listener = () => this.fetch().then(callback);
chrome.bookmarks.onCreated.addListener(listener);
chrome.bookmarks.onChanged.addListener(listener);
chrome.bookmarks.onRemoved.addListener(listener);
return () => {
chrome.bookmarks.onCreated.removeListener(listener);
chrome.bookmarks.onChanged.removeListener(listener);
chrome.bookmarks.onRemoved.removeListener(listener);
};
}
}

  1. User clicks “Login” in extension popup
  2. Extension opens OAuth URL in new tab
  3. User authenticates with WorkOS
  4. Callback sets session cookie
  5. Extension detects cookie, stores session
  6. Popup shows authenticated state
// In popup
const handleLogin = async () => {
const response = await fetch('https://app.bitmarks.sh/api/v1/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ returnTo: '/auth/callback' })
});
const { authorizationUrl } = await response.json();
// Open in new tab
chrome.tabs.create({ url: authorizationUrl });
};
lib/storage.ts
export async function getSession() {
const { session } = await chrome.storage.local.get('session');
return session;
}
export async function setSession(session: Session) {
await chrome.storage.local.set({ session });
}
export async function clearSession() {
await chrome.storage.local.remove('session');
}

async function setupEncryption(password: string) {
// Get salt from server
const response = await fetch('/api/v1/auth/session', {
credentials: 'include'
});
const { user } = await response.json();
// Derive key from password
const key = await deriveKey(password, user.salt);
// Store encrypted with device key
const deviceKey = await getOrCreateDeviceKey();
const encryptedKey = await encrypt(key, deviceKey);
await chrome.storage.local.set({ encryptedKey });
}
async function getEncryptionKey(): Promise<Uint8Array | null> {
const { encryptedKey } = await chrome.storage.local.get('encryptedKey');
if (!encryptedKey) return null;
const deviceKey = await getDeviceKey();
return decrypt(encryptedKey, deviceKey);
}

Terminal window
cd packages/webextension
bun run dev

This starts Vite in watch mode. Load the dist folder in Chrome.

Terminal window
bun run build
Terminal window
bun run test

  1. Check if logged in (popup shows user info)
  2. Verify sync is enabled in settings
  3. Check console for errors (chrome://extensions → “Errors”)
  4. Try manual sync (click sync button in popup)
  1. Clear extension data
  2. Try logging out and back in
  3. Check if cookies are blocked
  1. Reduce sync frequency in settings
  2. Disable unused data sources
  3. Check for large bookmark collections (>10k)

The extension requires these permissions:

{
"permissions": [
"bookmarks", // Access bookmarks
"storage", // Store settings
"identity", // OAuth flow
"tabs", // Open auth tab
"alarms" // Periodic sync
],
"optional_permissions": [
"history", // History sync
"tabGroups" // Tab group sync
],
"host_permissions": [
"https://app.bitmarks.sh/*",
"https://api.github.com/*"
]
}