Skip to content

Importing Bookmarks

BitMarks supports importing bookmarks from various sources including Chrome, Firefox, Safari, and other browsers.

SourceFormatStatus
ChromeHTML, JSONSupported
FirefoxHTML, JSONSupported
SafariHTMLSupported
EdgeHTMLSupported
BraveHTML, JSONSupported
GitHub StarsAPISupported

    1. Open Chrome and go to chrome://bookmarks
    2. Click the three-dot menu (⋮) in the top right
    3. Select “Export bookmarks”
    4. Save the HTML file
  1. 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) {
    // Bookmark
    items.push({
    title: a.textContent || '',
    url: a.getAttribute('href') || '',
    dateAdded: parseInt(a.getAttribute('add_date') || '0') * 1000,
    path
    });
    } else if (h3 && subDl) {
    // Folder
    const 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) : [];
    }
  2. import { encryptObject, hashData } from '@bitmarks.sh/core';
    async function importBookmarks(
    bookmarks: ChromeBookmark[],
    key: Uint8Array
    ) {
    // Transform to BitMarks format
    const 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 bookmark
    const 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 API
    const 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();
    }

    1. Open Firefox and press Ctrl+Shift+O (or Cmd+Shift+O on Mac)
    2. Click “Import and Backup” → “Export Bookmarks to HTML”
    3. Save the file
  1. Firefox HTML format is similar to Chrome:

    // Use the same parseBookmarksHtml function
    const bookmarks = parseBookmarksHtml(htmlContent);
    await importBookmarks(bookmarks, encryptionKey);

  1. You’ll need a GitHub Personal Access Token with read:user scope.

  2. 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;
    }
  3. 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 fields
    name: 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...
    }

The BitMarks browser extension provides a one-click import:

// In extension background script
chrome.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;
}

POST /api/v1/sync/import
Content-Type: application/json
X-Device-ID: chrome-extension
{
"bookmarks": [
{
"id": "bm_abc123",
"encrypted_data": "base64...",
"data_hash": "sha256...",
"date_added": 1735430400,
"date_modified": 1735430400
}
]
}
{
"imported": 100,
"imported_ids": ["bm_1", "bm_2", "..."],
"errors": [
{
"id": "bm_failed",
"error": "Duplicate bookmark ID"
}
],
"timestamp": 1735430600000
}

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);
}

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 };
}
// Usage
await importWithProgress(bookmarks, key, (progress) => {
console.log(`Import progress: ${Math.round(progress * 100)}%`);
});

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;
}