Importing Bookmarks
Importing Bookmarks
Section titled “Importing Bookmarks”BitMarks supports importing bookmarks from various sources including Chrome, Firefox, Safari, and other browsers.
Supported Sources
Section titled “Supported Sources”| Source | Format | Status |
|---|---|---|
| Chrome | HTML, JSON | Supported |
| Firefox | HTML, JSON | Supported |
| Safari | HTML | Supported |
| Edge | HTML | Supported |
| Brave | HTML, JSON | Supported |
| GitHub Stars | API | Supported |
Chrome Import
Section titled “Chrome Import”-
Export from Chrome
Section titled “Export from Chrome”- Open Chrome and go to
chrome://bookmarks - Click the three-dot menu (⋮) in the top right
- Select “Export bookmarks”
- Save the HTML file
- Open Chrome and go to
-
Parse the HTML
Section titled “Parse the HTML”interface ChromeBookmark {title: string;url?: string;dateAdded?: number;children?: ChromeBookmark[];}function parseBookmarksHtml(html: string): ChromeBookmark[] {const parser = new DOMParser();const doc = parser.parseFromString(html, 'text/html');const parseFolder = (dl: Element, path = ''): ChromeBookmark[] => {const items: ChromeBookmark[] = [];for (const dt of dl.querySelectorAll(':scope > dt')) {const a = dt.querySelector(':scope > a');const h3 = dt.querySelector(':scope > h3');const subDl = dt.querySelector(':scope > dl');if (a) {// Bookmarkitems.push({title: a.textContent || '',url: a.getAttribute('href') || '',dateAdded: parseInt(a.getAttribute('add_date') || '0') * 1000,path});} else if (h3 && subDl) {// Folderconst folderName = h3.textContent || 'Untitled';const newPath = path ? `${path}/${folderName}` : folderName;items.push(...parseFolder(subDl, newPath));}}return items;};const rootDl = doc.querySelector('dl');return rootDl ? parseFolder(rootDl) : [];} -
Encrypt and Upload
Section titled “Encrypt and Upload”import { encryptObject, hashData } from '@bitmarks.sh/core';async function importBookmarks(bookmarks: ChromeBookmark[],key: Uint8Array) {// Transform to BitMarks formatconst bitmarksBookmarks = bookmarks.map(bm => ({id: `bm_${crypto.randomUUID()}`,title: bm.title,url: bm.url,dateAdded: new Date(bm.dateAdded || Date.now()).toISOString(),path: bm.path,source: 'bookmarks' as const}));// Encrypt each bookmarkconst encrypted = await Promise.all(bitmarksBookmarks.map(async (bookmark) => {const data = JSON.stringify(bookmark);return {id: bookmark.id,encrypted_data: await encryptObject(bookmark, key),data_hash: await hashData(new TextEncoder().encode(data)),date_added: Math.floor(new Date(bookmark.dateAdded!).getTime() / 1000),date_modified: Math.floor(Date.now() / 1000)};}));// Import via APIconst response = await fetch('/api/v1/sync/import', {method: 'POST',headers: {'Content-Type': 'application/json','X-Device-ID': getDeviceId()},credentials: 'include',body: JSON.stringify({ bookmarks: encrypted })});return response.json();}
Firefox Import
Section titled “Firefox Import”-
Export from Firefox
Section titled “Export from Firefox”- Open Firefox and press
Ctrl+Shift+O(orCmd+Shift+Oon Mac) - Click “Import and Backup” → “Export Bookmarks to HTML”
- Save the file
- Open Firefox and press
-
Parse and Import
Section titled “Parse and Import”Firefox HTML format is similar to Chrome:
// Use the same parseBookmarksHtml functionconst bookmarks = parseBookmarksHtml(htmlContent);await importBookmarks(bookmarks, encryptionKey);
GitHub Stars Import
Section titled “GitHub Stars Import”-
Authenticate with GitHub
Section titled “Authenticate with GitHub”You’ll need a GitHub Personal Access Token with
read:userscope. -
Fetch Stars
Section titled “Fetch Stars”async function fetchGitHubStars(token: string): Promise<any[]> {const stars: any[] = [];let page = 1;while (true) {const response = await fetch(`https://api.github.com/user/starred?page=${page}&per_page=100`,{headers: {'Authorization': `Bearer ${token}`,'Accept': 'application/vnd.github.v3.star+json'}});if (!response.ok) break;const data = await response.json();if (data.length === 0) break;stars.push(...data);page++;}return stars;} -
Transform and Import
Section titled “Transform and Import”async function importGitHubStars(stars: any[], key: Uint8Array) {const bookmarks = stars.map(star => ({id: `star_${star.repo.id}`,title: star.repo.full_name,url: star.repo.html_url,dateAdded: star.starred_at,source: 'stars' as const,// GitHub-specific fieldsname: star.repo.name,full_name: star.repo.full_name,description: star.repo.description,stargazers_count: star.repo.stargazers_count,owner: {login: star.repo.owner.login,avatar_url: star.repo.owner.avatar_url}}));// Encrypt and import...}
Browser Extension Import
Section titled “Browser Extension Import”The BitMarks browser extension provides a one-click import:
// In extension background scriptchrome.bookmarks.getTree(async (tree) => { const bookmarks = flattenBookmarkTree(tree);
// Get encryption key from storage const { encryptionKey } = await chrome.storage.local.get('encryptionKey');
// Encrypt and import await importBookmarks(bookmarks, encryptionKey);});
function flattenBookmarkTree( nodes: chrome.bookmarks.BookmarkTreeNode[], path = ''): Bookmark[] { const bookmarks: Bookmark[] = [];
for (const node of nodes) { if (node.url) { bookmarks.push({ id: `bm_${node.id}`, title: node.title, url: node.url, dateAdded: new Date(node.dateAdded || Date.now()).toISOString(), path, source: 'bookmarks' }); }
if (node.children) { const newPath = path ? `${path}/${node.title}` : node.title; bookmarks.push(...flattenBookmarkTree(node.children, newPath)); } }
return bookmarks;}Bulk Import API
Section titled “Bulk Import API”Request Format
Section titled “Request Format”POST /api/v1/sync/importContent-Type: application/jsonX-Device-ID: chrome-extension
{ "bookmarks": [ { "id": "bm_abc123", "encrypted_data": "base64...", "data_hash": "sha256...", "date_added": 1735430400, "date_modified": 1735430400 } ]}Response Format
Section titled “Response Format”{ "imported": 100, "imported_ids": ["bm_1", "bm_2", "..."], "errors": [ { "id": "bm_failed", "error": "Duplicate bookmark ID" } ], "timestamp": 1735430600000}Handling Duplicates
Section titled “Handling Duplicates”BitMarks detects duplicates using the data_hash field:
async function importWithDeduplication(bookmarks: Bookmark[], key: Uint8Array) { // Get existing hashes const existingResponse = await fetch('/api/v1/sync/pull', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ since: 0, limit: 10000 }) });
const { changes } = await existingResponse.json(); const existingHashes = new Set(changes.map(c => c.data_hash));
// Filter out duplicates const newBookmarks = []; for (const bookmark of bookmarks) { const hash = await hashData( new TextEncoder().encode(JSON.stringify(bookmark)) );
if (!existingHashes.has(hash)) { newBookmarks.push(bookmark); } }
console.log(`Importing ${newBookmarks.length} new bookmarks (${bookmarks.length - newBookmarks.length} duplicates skipped)`);
// Import new bookmarks return importBookmarks(newBookmarks, key);}Progress Tracking
Section titled “Progress Tracking”For large imports, track progress:
async function importWithProgress( bookmarks: Bookmark[], key: Uint8Array, onProgress: (progress: number) => void) { const batchSize = 100; const batches = Math.ceil(bookmarks.length / batchSize); let imported = 0;
for (let i = 0; i < batches; i++) { const batch = bookmarks.slice(i * batchSize, (i + 1) * batchSize);
// Encrypt batch const encrypted = await Promise.all( batch.map(async (bm) => ({ id: bm.id, encrypted_data: await encryptObject(bm, key), data_hash: await hashData(new TextEncoder().encode(JSON.stringify(bm))) })) );
// Import batch const response = await fetch('/api/v1/sync/import', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ bookmarks: encrypted }) });
const result = await response.json(); imported += result.imported;
// Report progress onProgress((i + 1) / batches); }
return { imported };}
// Usageawait importWithProgress(bookmarks, key, (progress) => { console.log(`Import progress: ${Math.round(progress * 100)}%`);});Error Handling
Section titled “Error Handling”async function safeImport(bookmarks: Bookmark[], key: Uint8Array) { const results = { success: [] as string[], failed: [] as { id: string; error: string }[] };
for (const bookmark of bookmarks) { try { const encrypted = await encryptObject(bookmark, key); const hash = await hashData( new TextEncoder().encode(JSON.stringify(bookmark)) );
const response = await fetch('/api/v1/sync/import', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ bookmarks: [{ id: bookmark.id, encrypted_data: encrypted, data_hash: hash }] }) });
if (response.ok) { results.success.push(bookmark.id); } else { const error = await response.json(); results.failed.push({ id: bookmark.id, error: error.error }); } } catch (error) { results.failed.push({ id: bookmark.id, error: error instanceof Error ? error.message : 'Unknown error' }); } }
return results;}