首页 / 浏览器扩展开发入门教程 / bookmarks 书签

浏览器扩展开发入门教程

bookmarks 书签

本教程共 56 篇 · 第 39 篇 · 更新于 2026-08-13 · 约 7 分钟阅读

bookmarks书签树creategetChildrenremoveTree文件夹onCreated

本节目标:学完能操作浏览器的书签树,会新建书签和文件夹、读取子节点、删除单层与整棵子树。

39-1 API 概览与权限

bookmarks API 让扩展读写浏览器书签。典型场景有:一键收藏当前页、批量整理书签、按规则自动归类网址、书签搜索增强。

它操作的是整棵书签树,不是单条记录那么简单。理解树结构,是用好这个 API 的前提。

权限很简单,只要一个字符串。

{
  "manifest_version": 3,
  "permissions": ["bookmarks"]
}

bookmarks 不需要 host 权限,因为它动的是浏览器自身的书签数据,和具体网页无关。

Note

书签 API 不需要 host_permissions,这和 cookies 不同。只要声明 “bookmarks” 权限,就能读写全量书签树。

39-2 书签树的结构

浏览器把所有书签组织成一棵「树」。树有根节点,下面挂文件夹和书签,文件夹里还能再挂文件夹和书签,层层嵌套。

根节点的 id 固定是 “0”。在它之下,Chrome 预置了三个特殊文件夹:

  • id 为 “1” 的是「书签栏」,就是浏览器顶部那条常用书签。
  • id 为 “2” 的是「其他书签」,侧边栏里收起来的那些。
  • id 为 “3” 的是「移动设备书签」,给手机端同步的书签用,桌面 UI 里通常隐藏,但 getTree 能读到。

你新建的书签或文件夹,必须挂在某一个父节点之下。如果不指定 parentId,默认就挂到「其他书签」里。

每个节点都是一个 BookmarkTreeNode 对象,常见字段有:id(唯一标识)、parentId(父节点 id)、index(在父节点中的排序位置)、title(标题)、url(网址)、children(子节点数组)、dateAdded(添加时间)。

关键区别:带 url 的是书签,不带 url 的是文件夹。读 children 时,文件夹的 url 字段是 undefined。

Tip

写逻辑时靠「有没有 url」来区分节点类型。文件夹没有 url,书签一定有 url,这是最稳的判断方式。

39-3 创建书签与文件夹:create

新建节点用 chrome.bookmarks.create,传一个配置对象。配了 url 就是书签,不配 url 就是文件夹。

// 新建一个书签,挂到书签栏
chrome.bookmarks.create({
  parentId: "1",
  title: "码上学",
  url: "https://example.com"
}, (node) => {
  console.log("新建节点 id:", node.id);
});

// 新建一个文件夹(不写 url)
chrome.bookmarks.create({
  parentId: "1",
  title: "我的收藏"
}, (folder) => {
  console.log("文件夹 id:", folder.id);
});

parentId 决定挂在哪里,不写就落到「其他书签」。index 可以指定位置,不写就追加到末尾。

create 的回调会返回完整的节点对象,里面带着系统生成的 id。后续要移动、删除这个节点,靠的就是这个 id。

Tip

想先建文件夹再往里塞书签,一定要等文件夹的 create 回调拿到 id,再用那个 id 当 parentId,别凭空猜 id。

39-4 读取子节点:getChildren 与 getTree

只读某个文件夹下的直接子项,用 chrome.bookmarks.getChildren,传父节点 id。

chrome.bookmarks.getChildren("1", (children) => {
  children.forEach((c) => {
    if (c.url) {
      console.log("书签:", c.title, c.url);
    } else {
      console.log("文件夹:", c.title);
    }
  });
});

它只返回「直接孩子」,不会递归往下钻。如果你要某一节点以下的整棵子树,用 chrome.bookmarks.getSubTree。

chrome.bookmarks.getSubTree("1", (nodes) => {
  console.log("书签栏整棵树:", nodes);
});

想要整棵书签树的完整结构,用 chrome.bookmarks.getTree,它会从根节点 “0” 一路返回所有层级。

另外,chrome.bookmarks.get(id) 可以按单个 id 取节点,适合你已经知道某个 id、只想拿它详情的情况。

Note

getChildren 不递归,getSubTree 递归一层根往下全拿。要遍历全量书签,getTree 最省事,但数据量可能很大,注意性能。

39-5 删除:remove 与 removeTree 的区别

删除是这一章最容易出错的地方,因为有两个方法,语义不同。

chrome.bookmarks.remove 只能删「单个书签」或「空的文件夹」。如果你拿它去删一个有内容的文件夹,会直接报错。

chrome.bookmarks.remove("书签节点id", () => {
  console.log("已删除该书签");
});

chrome.bookmarks.removeTree 才是删文件夹的正确姿势,它会连文件夹带里面所有子项一起删掉。

chrome.bookmarks.removeTree("文件夹id", () => {
  console.log("文件夹及其内容已删除");
});

一句话:删单个用 remove,删一整个分支用 removeTree。

Tip

不确定目标是文件还是文件夹时,先 get 一下看有没有 children 或 url,再决定用哪个删除方法,能避免报错打断流程。

39-6 移动与更新:move 和 update

想把一个书签或文件夹挪位置,用 chrome.bookmarks.move,可以改 parentId(移到别的文件夹)和 index(调整顺序)。

chrome.bookmarks.move("节点id", {
  parentId: "1",
  index: 0
}, (node) => {
  console.log("已移动到书签栏首位");
});

想改标题或网址,用 chrome.bookmarks.update。文件夹没有 url,所以更新文件夹时只能改 title。

chrome.bookmarks.update("节点id", {
  title: "新标题",
  url: "https://new-example.com"
}, (node) => {
  console.log("已更新");
});

这两个方法都有回调,返回更新后的节点对象,方便你接着做后续处理。

39-7 搜索:search

想按关键字找书签,不用自己遍历树,用 chrome.bookmarks.search 直接查。

chrome.bookmarks.search("教程", (results) => {
  results.forEach((r) => console.log(r.title, r.url));
});

search 的参数是字符串,会同时匹配标题和网址。它返回所有命中的节点,包含文件夹和书签。

你也可以传一个对象做更精细的查询,比如只按 url 匹配。实际做「书签去重」「快速定位」类功能时,search 比手写遍历方便得多。

Note

search 的结果可能同时包含文件夹和书签,处理前先看 url 是否存在,别把文件夹当书签用。

39-8 监听变化:onCreated / onRemoved

如果你希望书签被用户手动改动时,扩展也能感知,用监听器。

chrome.bookmarks.onCreated.addListener((id, node) => {
  console.log("新增了节点:", node.title);
});

chrome.bookmarks.onRemoved.addListener((id, removeInfo) => {
  console.log("被删的节点 id:", id);
  console.log("是否连带删了子树:", removeInfo.node.children ? "是" : "否");
});

onCreated 回调给新建节点的 id 和完整对象。onRemoved 的 removeInfo 里带有被删节点的快照,以及它是否是一个被整棵移除的文件夹(注意:官方未明示快照是否包含 children 数组,用 node.children 判断是否整棵删除前,需实测确认)。

还有 onChanged(标题/网址改动)和 onMoved(位置移动),覆盖书签树的全部变更类型。做书签同步、备份类扩展时,这几个监听器是核心。

39-9 常见坑与调试

第一个坑,把 remove 用在含内容的文件夹上。要注意:有内容的文件夹用 removeTree,remove 只接受单书签或空文件夹。

第二个坑,建子项时 parentId 还没生成。一定要在父文件夹的 create 回调里拿到 id,再去建子项,否则会挂在错误的位置或失败。

第三个坑,分不清节点类型。处理 children 或 search 结果时,永远先判断 url 是否存在,再决定当书签还是文件夹处理。

调试书签 API,最直接的方法是打开后台服务工作者(Service Worker)控制台,敲 chrome.bookmarks.getTree(console.log) 看整棵树长什么样,对照 id 来验证你的逻辑。

Tip

不确定某个 id 对应什么节点,先用 chrome.bookmarks.get(id) 看它的 parentId、url、children,确认类型再操作。

39-10 小结

bookmarks API 的本质是操作一棵「树」。根节点是 “0”,下面三个预置文件夹「书签栏」(“1”)、「其他书签」(“2”) 和「移动设备书签」(“3”),前两个是你最常挂接的地方。

四个动作覆盖大部分需求:create 建、getChildren/getTree 读、move/update 改、remove/removeTree 删。删除方法的选择是重点——单条用 remove,整枝用 removeTree。

搜索用 search,监听用 onCreated/onRemoved 等,省去自己遍历的麻烦。

最后提醒:书签是用户重要的个人数据,批量改动前最好先确认,删除操作(尤其 removeTree)不可轻易撤销,做自动化整理时务必谨慎。