Working with Bookmarks in Chrome Extensions — Developer Guide

26 min read

Working with Bookmarks in Chrome Extensions

Overview

The Chrome Bookmarks API (chrome.bookmarks) is one of the most powerful extension APIs available, allowing you to create, read, update, and delete bookmarks programmatically. Whether you’re building a bookmark manager, a bookmark importer, or integrating bookmark functionality into your extension, this guide covers everything you need to know.

Prerequisites

Before using the Bookmarks API, add the "bookmarks" permission to your manifest.json:

{
  "manifest_version": 3,
  "name": "My Bookmark Extension",
  "version": "1.0",
  "permissions": ["bookmarks"]
}

Important: The bookmarks permission grants access to all bookmarks. You cannot restrict access to specific folders.

Understanding the Bookmark Tree Structure

The Chrome Bookmarks API organizes bookmarks in a hierarchical tree structure. Each node is a BookmarkTreeNode with these properties:

Property Type Description
id string Unique identifier for the node
parentId string ID of parent folder (null for root)
title string Display title
url string URL of the bookmark (undefined for folders)
index number Position within parent’s children
dateAdded number Unix timestamp when created
dateGroupModified number Unix timestamp of last modification
children array Child nodes (only when retrieving subtrees)

The tree root contains three default folders:

  1. Bookmarks Bar (typically ID “1”)
  2. Other Bookmarks (typically ID “2”)
  3. Mobile Bookmarks (typically ID “3”)

Reading Bookmarks

Get the Entire Tree

chrome.bookmarks.getTree((tree) => {
  const root = tree[0];
  const bookmarksBar = root.children[0];
  const otherBookmarks = root.children[1];
  
  console.log("Bookmarks Bar:", bookmarksBar.title);
  console.log("Children:", bookmarksBar.children);
});

Get Recent Bookmarks

// Get the 10 most recently added bookmarks
chrome.bookmarks.getRecent(10, (results) => {
  results.forEach(bookmark => {
    console.log(`${bookmark.title}: ${bookmark.url}`);
  });
});

Get a Specific Bookmark

// Get bookmarks by ID
chrome.bookmarks.get("12345", (results) => {
  if (results.length > 0) {
    console.log("Found:", results[0]);
  }
});

// Get multiple bookmarks by ID
chrome.bookmarks.get(["12345", "67890"], (results) => {
  console.log("Found bookmarks:", results);
});

Searching Bookmarks

// Search by title or URL
chrome.bookmarks.search("tutorial", (results) => {
  results.forEach(b => console.log(b.title, b.url));
});

Search with Query Object

// Search by URL
chrome.bookmarks.search({ url: "https://developer.chrome.com" }, (results) => {
  console.log("URL matches:", results);
});

// Search by title
chrome.bookmarks.search({ title: "My Bookmark" }, (results) => {
  console.log("Title matches:", results);
});

// Search multiple criteria
chrome.bookmarks.search({ url: "https://example.com" }, (results) => {
  results.forEach(b => console.log(b));
});

Practical Search Example

function findDuplicates() {
  const urlMap = new Map();
  
  chrome.bookmarks.getTree((tree) => {
    function traverse(nodes) {
      nodes.forEach(node => {
        if (node.url) {
          if (urlMap.has(node.url)) {
            console.log(`Duplicate: "${node.title}" and "${urlMap.get(node.url)}"`);
          } else {
            urlMap.set(node.url, node.title);
          }
        }
        if (node.children) {
          traverse(node.children);
        }
      });
    }
    traverse(tree);
  });
}

Creating Bookmarks

Create a Simple Bookmark

// Create a bookmark in the bookmarks bar
chrome.bookmarks.create({
  parentId: "1",  // Bookmarks Bar ID
  title: "Chrome Extensions Docs",
  url: "https://developer.chrome.com/docs/extensions/"
}, (newBookmark) => {
  console.log("Created bookmark with ID:", newBookmark.id);
});

Create a Folder

// Create a new folder
chrome.bookmarks.create({
  parentId: "1",
  title: "Work Resources"
}, (newFolder) => {
  console.log("Created folder with ID:", newFolder.id);
});

Create Nested Structure

// Create a folder with subfolders
function createProjectStructure() {
  // First, create the main folder
  chrome.bookmarks.create({
    parentId: "1",
    title: "My Projects"
  }, (projectsFolder) => {
    // Then create subfolders
    chrome.bookmarks.create({
      parentId: projectsFolder.id,
      title: "In Progress"
    }, () => {
      chrome.bookmarks.create({
        parentId: projectsFolder.id,
        title: "Completed"
      }, () => {
        console.log("Project structure created!");
      });
    });
  });
}

Create Bookmark with Callbacks Using Promises

// Wrap callbacks in promises for cleaner async/await
const bookmarksAPI = {
  create: (props) => new Promise((resolve) => {
    chrome.bookmarks.create(props, resolve);
  }),
  
  getTree: () => new Promise((resolve) => {
    chrome.bookmarks.getTree(resolve);
  }),
  
  search: (query) => new Promise((resolve) => {
    chrome.bookmarks.search(query, resolve);
  })
};

// Usage with async/await
async function addProjectBookmark() {
  const projects = await bookmarksAPI.search({ title: "My Projects" });
  const parentId = projects[0]?.id || "1";
  
  const bookmark = await bookmarksAPI.create({
    parentId,
    title: "New Project",
    url: "https://example.com"
  });
  
  console.log("Created:", bookmark);
}

Updating Bookmarks

Update Bookmark Title and URL

// Update a bookmark's title
chrome.bookmarks.update("12345", { title: "New Title" }, (result) => {
  console.log("Updated:", result);
});

// Update URL
chrome.bookmarks.update("12345", { url: "https://new-url.com" }, (result) => {
  console.log("URL updated:", result);
});

// Update both
chrome.bookmarks.update("12345", {
  title: "Updated Title",
  url: "https://updated-url.com"
}, (result) => {
  console.log("Bookmark updated:", result);
});

Move Bookmark to Different Folder

// Move a bookmark to a different folder
chrome.bookmarks.move("12345", { parentId: "2" }, (result) => {
  console.log("Moved to Other Bookmarks");
});

// Move and change position
chrome.bookmarks.move("12345", { parentId: "2", index: 0 }, (result) => {
  console.log("Moved to first position in Other Bookmarks");
});

Reorder Bookmarks in a Folder

// Get current bookmarks and reorder
function reorderBookmarks(parentId, newOrder) {
  newOrder.forEach((bookmarkId, index) => {
    chrome.bookmarks.move(bookmarkId, { parentId, index });
  });
}

Deleting Bookmarks

Delete a Single Bookmark

// Delete by ID
chrome.bookmarks.remove("12345", () => {
  console.log("Bookmark deleted");
});

Delete a Folder (and all contents)

// Remove a folder and all its children
chrome.bookmarks.removeTree("67890", () => {
  console.log("Folder and contents deleted");
});

Delete Multiple Bookmarks

// Delete multiple bookmarks
const idsToDelete = ["12345", "67890", "11111"];
idsToDelete.forEach(id => {
  chrome.bookmarks.remove(id);
});

Safe Delete with Confirmation

function safeDeleteBookmark(id) {
  chrome.bookmarks.get(id, (results) => {
    if (results.length === 0) {
      console.log("Bookmark not found");
      return;
    }
    
    const bookmark = results[0];
    const isFolder = !bookmark.url;
    
    if (confirm(`Delete "${bookmark.title}"?`)) {
      if (isFolder) {
        chrome.bookmarks.removeTree(id, () => console.log("Folder deleted"));
      } else {
        chrome.bookmarks.remove(id, () => console.log("Bookmark deleted"));
      }
    }
  });
}

Bookmark Events

Listen for changes to keep your extension in sync.

Listen for Bookmark Creation

chrome.bookmarks.onCreated.addListener((id, bookmark) => {
  console.log(`New bookmark created: "${bookmark.title}"`);
  console.log("ID:", id);
  console.log("URL:", bookmark.url);
});

Listen for Bookmark Deletion

chrome.bookmarks.onRemoved.addListener((id, removeInfo) => {
  console.log(`Bookmark removed: "${removeInfo.node.title}"`);
  console.log("Parent ID:", removeInfo.parentId);
  console.log("Was folder:", !removeInfo.node.url);
});

Listen for Bookmark Updates

chrome.bookmarks.onChanged.addListener((id, changeInfo) => {
  console.log(`Bookmark ${id} changed:`);
  console.log("New title:", changeInfo.title);
  console.log("New URL:", changeInfo.url);
});

Listen for Bookmark Moves

chrome.bookmarks.onMoved.addListener((id, moveInfo) => {
  console.log(`Bookmark moved from ${moveInfo.parentId} to ${moveInfo.index}`);
});

Listen for Folder Reorganization

chrome.bookmarks.onChildrenReordered.addListener((id, reorderInfo) => {
  console.log(`Folder ${id} children reordered:`, reorderInfo.childIds);
});

Complete Event Handler Example

// background.js - Handle all bookmark events
chrome.bookmarks.onCreated.addListener((id, bookmark) => {
  console.log("Created:", bookmark.title);
  // Update your UI or storage
});

chrome.bookmarks.onRemoved.addListener((id, info) => {
  console.log("Removed:", info.node.title);
  // Remove from your UI or storage
});

chrome.bookmarks.onChanged.addListener((id, info) => {
  console.log("Changed:", info.title || info.url);
  // Update your UI or storage
});

chrome.bookmarks.onMoved.addListener((id, info) => {
  console.log("Moved to folder:", info.parentId);
  // Reorder in your UI
});

chrome.bookmarks.onChildrenReordered.addListener((id, info) => {
  console.log("Reordered children in folder:", id);
  // Update display order
});

Organizing Folders

Create Nested Folder Structure

function createNestedFolders(parentId, path) {
  const [current, ...rest] = path;
  
  return new Promise((resolve) => {
    chrome.bookmarks.create({ parentId, title: current }, (folder) => {
      if (rest.length === 0) {
        resolve(folder);
      } else {
        createNestedFolders(folder.id, rest).then(resolve);
      }
    });
  });
}

// Usage: Create "Work/Projects/2024/Q1"
createNestedFolders("1", ["Work", "Projects", "2024", "Q1"])
  .then(folder => console.log("Created nested structure at:", folder.id));

Flatten Nested Structure

function flattenFolder(folderId) {
  chrome.bookmarks.getChildren(folderId, (children) => {
    children.forEach(child => {
      if (child.children) {
        // It's a folder, recurse
        flattenFolder(child.id);
      } else if (child.url) {
        // Move bookmark to parent
        chrome.bookmarks.move(child.id, { parentId: folderId }, () => {
          console.log("Moved:", child.title);
        });
      }
    });
  });
}

Sort Folder Contents

function sortFolderByTitle(folderId) {
  chrome.bookmarks.getChildren(folderId, (children) => {
    const bookmarks = children.filter(c => c.url);
    const folders = children.filter(c => c.children);
    
    // Sort bookmarks alphabetically
    bookmarks.sort((a, b) => a.title.localeCompare(b.title));
    
    // Put folders first, then bookmarks
    const sorted = [...folders, ...bookmarks];
    
    sorted.forEach((item, index) => {
      chrome.bookmarks.move(item.id, { parentId: folderId, index });
    });
  });
}

Import/Export Patterns

Export Bookmarks to JSON

function exportBookmarks() {
  chrome.bookmarks.getTree((tree) => {
    const exportData = {
      exportDate: new Date().toISOString(),
      version: "1.0",
      bookmarks: tree[0]
    };
    
    const blob = new Blob([JSON.stringify(exportData, null, 2)], {
      type: "application/json"
    });
    
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = `bookmarks-${new Date().toISOString().split("T")[0]}.json`;
    a.click();
    URL.revokeObjectURL(url);
  });
}

Import Bookmarks from JSON

function importBookmarks(jsonData) {
  const data = JSON.parse(jsonData);
  const imported = data.bookmarks;
  
  function importNode(node, parentId) {
    return new Promise((resolve) => {
      const options = {
        parentId,
        title: node.title
      };
      
      if (node.url) {
        // It's a bookmark
        options.url = node.url;
      }
      
      chrome.bookmarks.create(options, (created) => {
        if (node.children && node.children.length > 0) {
          const promises = node.children.map(child => 
            importNode(child, created.id)
          );
          Promise.all(promises).then(resolve);
        } else {
          resolve(created);
        }
      });
    });
  }
  
  return importNode(imported, "2"); // Import to Other Bookmarks
}

// Handle file input
document.getElementById("importFile").addEventListener("change", (e) => {
  const file = e.target.files[0];
  const reader = new FileReader();
  reader.onload = (event) => {
    importBookmarks(event.target.result)
      .then(() => console.log("Import complete!"))
      .catch(err => console.error("Import failed:", err));
  };
  reader.readAsText(file);
});

Import from HTML Bookmark Export

function importFromHTML(htmlContent) {
  const parser = new DOMParser();
  const doc = parser.parseFromString(htmlContent, "text/html");
  const links = doc.querySelectorAll("a");
  
  let promise = Promise.resolve("1"); // Start at bookmarks bar
  
  links.forEach(link => {
    const href = link.getAttribute("href");
    const title = link.textContent;
    
    if (href && href.startsWith("http")) {
      promise = promise.then(parentId => {
        return new Promise(resolve => {
          chrome.bookmarks.create({ parentId, title, url: href }, () => {
            resolve(parentId);
          });
        });
      });
    }
  });
  
  return promise;
}

Bookmark Bar Interaction

Add Button to Bookmark Bar

// In manifest.json
{
  "action": {
    "default_icon": "icon.png",
    "default_title": "Save to My App"
  }
}

// In background.js
chrome.action.onClicked.addListener((tab) => {
  // Get current page info and create bookmark
  chrome.bookmarks.create({
    parentId: "1",
    title: tab.title,
    url: tab.url
  }, (bookmark) => {
    console.log("Bookmarked:", bookmark.title);
  });
});

Context Menu for Bookmarks

// Create context menu for bookmarks
chrome.contextMenus.create({
  id: "addToMyApp",
  title: "Add to My Bookmark App",
  contexts: ["bookmark"]
});

chrome.contextMenus.onClicked.addListener((info, tab) => {
  if (info.menuItemId === "addToMyApp") {
    // Get bookmark details and add tags or move to specific folder
    chrome.bookmarks.get(info.bookmarkId, (results) => {
      const bookmark = results[0];
      console.log("Adding:", bookmark.title);
      // Your custom logic here
    });
  }
});

Check if Bookmark Exists

function isBookmarked(url) {
  return new Promise((resolve) => {
    chrome.bookmarks.search({ url }, (results) => {
      resolve(results.length > 0);
    });
  });
}

// Usage
isBookmarked("https://example.com").then(isBookmarked => {
  if (isBookmarked) {
    console.log("This page is bookmarked!");
  }
});

Get All Bookmarks from Bar

function getBookmarksBarBookmarks() {
  return new Promise((resolve) => {
    chrome.bookmarks.getTree((tree) => {
      const bookmarksBar = tree[0].children[0];
      resolve(bookmarksBar.children || []);
    });
  });
}

Complete Example: Bookmark Manager Service

Here’s a complete service class that wraps the Bookmarks API:

// bookmarksService.js
class BookmarksService {
  constructor() {
    this.cache = null;
    this.listeners = new Set();
    
    // Set up event listeners
    this.setupListeners();
  }
  
  setupListeners() {
    chrome.bookmarks.onCreated.addListener((id, bookmark) => {
      this.invalidateCache();
      this.notifyListeners("created", { id, bookmark });
    });
    
    chrome.bookmarks.onRemoved.addListener((id, info) => {
      this.invalidateCache();
      this.notifyListeners("removed", { id, info });
    });
    
    chrome.bookmarks.onChanged.addListener((id, info) => {
      this.invalidateCache();
      this.notifyListeners("changed", { id, info });
    });
    
    chrome.bookmarks.onMoved.addListener((id, info) => {
      this.invalidateCache();
      this.notifyListeners("moved", { id, info });
    });
  }
  
  invalidateCache() {
    this.cache = null;
  }
  
  subscribe(callback) {
    this.listeners.add(callback);
    return () => this.listeners.delete(callback);
  }
  
  notifyListeners(event, data) {
    this.listeners.forEach(cb => cb(event, data));
  }
  
  async getTree() {
    if (this.cache) return this.cache;
    return new Promise(resolve => {
      chrome.bookmarks.getTree(tree => {
        this.cache = tree;
        resolve(tree);
      });
    });
  }
  
  async search(query) {
    return new Promise(resolve => {
      chrome.bookmarks.search(query, resolve);
    });
  }
  
  async create(props) {
    return new Promise(resolve => {
      chrome.bookmarks.create(props, resolve);
    });
  }
  
  async update(id, props) {
    return new Promise(resolve => {
      chrome.bookmarks.update(id, props, resolve);
    });
  }
  
  async move(id, destination) {
    return new Promise(resolve => {
      chrome.bookmarks.move(id, destination, resolve);
    });
  }
  
  async remove(id) {
    return new Promise(resolve => {
      chrome.bookmarks.remove(id, resolve);
    });
  }
  
  async removeTree(id) {
    return new Promise(resolve => {
      chrome.bookmarks.removeTree(id, resolve);
    });
  }
  
  async getFolderContents(folderId) {
    return new Promise(resolve => {
      chrome.bookmarks.getChildren(folderId, resolve);
    });
  }
}

// Usage
const bookmarks = new BookmarksService();

// Subscribe to changes
const unsubscribe = bookmarks.subscribe((event, data) => {
  console.log("Bookmark event:", event, data);
});

// Search bookmarks
bookmarks.search("tutorial").then(results => {
  console.log("Found:", results);
});

// Create a bookmark
bookmarks.create({
  parentId: "1",
  title: "My Extension",
  url: "https://example.com"
}).then(bookmark => {
  console.log("Created:", bookmark.id);
});

Part of the Chrome Extension Guide by theluckystrike. Built at zovo.one.

No previous article
No next article