1. 零基础用 ChatGPT 写 Chrome 扩展,到底难在哪
很多人第一次听到「用 ChatGPT 构建 Chrome 浏览器扩展」,脑子里冒出来的第一个念头是:我连 JavaScript 都没系统学过,能行吗?我实测下来的结论是——能行,但前提是你得知道整个流程里哪些环节 AI 能帮你、哪些环节必须你自己动手确认。Chrome 扩展(Chrome Extension)本质上就是一堆 HTML、CSS、JavaScript 文件,加上一个叫 manifest.json 的配置文件,打包成一个文件夹,浏览器就能加载运行。它不是什么黑魔法,也不需要编译打包成 exe,你写完直接拖进浏览器就能跑。
那为什么很多人卡住?我总结下来有三个真实的坑。第一个坑是 manifest 版本混乱。网上大量教程还在讲 Manifest V2,但 Chrome 从 2023 年开始主推 Manifest V3,V2 的background.scripts写法在 V3 里直接报错,你照着老教程抄,加载时就会看到「Manifest version 2 is deprecated」这类提示。第二个坑是权限声明和实际调用对不上,比如你在 popup.js 里调了chrome.browsingData,但 manifest 里没写browsingData权限,运行时直接抛 undefined。第三个坑是 AI 生成的代码「看起来对、跑起来错」,因为 ChatGPT 不知道你的 Chrome 版本、不知道你文件夹里到底有没有图标文件,它给的代码需要你逐行核对。
这篇文章要解决的就是这三个坑。我会带你从零做一个真实可用的扩展原型——一个「一键清理浏览器缓存」的小工具,完整走一遍需求拆解、manifest 配置、popup 页面、content script、本地加载调试、直到打包上架的流程。同时我会演示怎么用 TaoToken 这个统一 Key 通道来调用模型辅助生成代码,这样你就不用在不同 AI 工具之间来回切换、也不用把 Key 散落在各个平台。适合谁看?适合完全没写过扩展、但会用电脑、愿意照着步骤敲代码的零基础开发者。你不需要精通 JS,但需要能看懂基本的函数和事件绑定。
先说清楚一个预期:AI 不会一次给你完美代码。你和它沟通的指令越清晰,生成的代码 bug 越少。我试过用一句「帮我写个清缓存的扩展」去问,得到的代码缺权限、缺图标、popup 路径还写错;但当我按「文件结构 + 每个文件的职责 + 具体 API + 权限清单」这样结构化地提问,基本两三轮就能跑通。所以这篇文章的重点不是「复制粘贴」,而是教你一套可复用的提问和验证方法。
2. TaoToken 统一 Key 通道:接入前的准备与配置
在正式写代码之前,先解决「用哪个模型来辅助生成代码」这件事。你当然可以直接开 ChatGPT 网页版,但实际开发中你会发现一个问题:写扩展时你需要在编辑器、浏览器、AI 对话之间反复切换,而且不同模型的 Key 管理很麻烦。TaoToken 的思路是提供一个统一的 API 通道,你用同一个 Key 就能调用多种模型,省去到处注册、到处贴 Key 的麻烦。官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 端点是 https://taotoken.net/api 。
这里要强调一点:TaoToken 是合规的模型调用通道,不是所谓「中转」的灰色服务,你把它理解成一个统一的 API 网关就行。它的价值在于:你在写扩展的过程中,可以让编辑器里的 AI 插件、命令行工具、甚至你自己写的小脚本,都通过同一个 Base URL 和同一个 Key 去请求模型,不用为每个工具单独配一套凭证。
接入的核心三件套永远是这三个:Base URL、API Key、Model ID。缺一个都调不通。Base URL 填https://taotoken.net/api,API Key 在你注册后到控制台的 API Keys 页面生成,Model ID 则根据你要用的模型填对应的标识。下面是一个标准的请求示例,你可以先用 curl 验证通道是否通:
curl https://taotoken.net/api/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer 你的API_KEY" \ -d '{ "model": "你的Model_ID", "messages": [ {"role": "user", "content": "用一句话解释 Chrome 扩展的 manifest.json 是做什么的"} ] }'如果返回里能看到choices字段和模型输出内容,说明通道正常。如果返回 401,说明 Key 错了或者没带Bearer前缀;如果返回local proxy failed之类的错误,通常是网络层或 Base URL 写错,检查是不是把/api漏了或者多写了/v1重复路径。
对于长期做编码和 Agent 场景的读者,可以考虑 Coding Plan,它更适合高频调用;如果只是想验证某个模型效果,用模型对话页面就够了;需要管理多个 Key 就去控制台。这几个入口分别是:模型对话 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 、Coding Plan https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 、控制台 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite 、API Keys https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 。接入文档在 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,遇到参数问题先查文档。
配置好通道后,你在写扩展时就可以这样用:把 manifest、popup 的代码片段丢给模型,让它帮你补全或纠错,而不是从零生成。比如你可以问「这是我的 manifest.json,Manifest V3,我想加一个点击图标弹出 popup 的功能,帮我检查权限和 action 字段是否完整」。这种带上下文的提问,比空泛地问「怎么写扩展」有效得多。
3. 可复制配置:manifest.json 与 popup 完整代码
这一节是全文的核心,给你可以直接复制、改改就能跑的完整配置。先建一个文件夹,比如叫cache-cleaner,然后在里面创建下面这些文件。整个扩展的文件结构是这样的:
cache-cleaner/ ├── manifest.json ├── popup.html ├── popup.js ├── style.css ├── background.js └── icons/ ├── icon-16.png ├── icon-32.png ├── icon-48.png └── icon-128.png图标文件你可以先用任意 PNG 占位,尺寸对不上 Chrome 也能加载,只是显示会拉伸。重点是 manifest.json,这是整个扩展的身份证,Manifest V3 的写法如下:
{ "manifest_version": 3, "name": "Cache Cleaner", "version": "1.0.0", "description": "一键清理浏览器缓存,支持按时间范围清理。", "permissions": ["browsingData", "storage"], "action": { "default_icon": { "16": "icons/icon-16.png", "32": "icons/icon-32.png", "48": "icons/icon-48.png", "128": "icons/icon-128.png" }, "default_popup": "popup.html", "default_title": "清理缓存" }, "background": { "service_worker": "background.js" }, "icons": { "16": "icons/icon-16.png", "48": "icons/icon-48.png", "128": "icons/icon-128.png" } }注意几个关键点。manifest_version必须是 3,写 2 会被 Chrome 拒绝加载。permissions里browsingData是清理缓存必须的,storage用来存清理记录。action.default_popup指向 popup.html,这是点击图标后弹出的界面。background.service_worker在 V3 里是单个 JS 文件,不再是 V2 的数组写法。
接下来是 popup.html,它定义弹窗的界面结构:
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="utf-8"> <title>清理缓存</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <h1>清理缓存</h1> <button id="allHistory">所有历史记录</button> <button id="pastMonth">过去一个月</button> <button id="pastWeek">过去一周</button> <button id="pastDay">过去一天</button> <button id="pastHour">过去一小时</button> <button id="pastMinute">过去一分钟</button> <p id="lastCleared"></p> <script src="popup.js"></script> </body> </html>popup.js 负责按钮点击后的实际清理逻辑,这里用chrome.browsingData.removeCache按时间戳清理:
function formatDate(date) { const d = new Date(date); const options = { year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit' }; return d.toLocaleDateString('zh-CN', options); } function showCleared() { const p = document.getElementById('lastCleared'); p.textContent = '已成功清除缓存 ' + formatDate(Date.now()); } function clearSince(since) { chrome.browsingData.removeCache({ since: since }, function () { showCleared(); }); } document.getElementById('allHistory').addEventListener('click', function () { clearSince(0); }); document.getElementById('pastMonth').addEventListener('click', function () { const d = new Date(); d.setMonth(d.getMonth() - 1); clearSince(d.getTime()); }); document.getElementById('pastWeek').addEventListener('click', function () { const d = new Date(); d.setDate(d.getDate() - 7); clearSince(d.getTime()); }); document.getElementById('pastDay').addEventListener('click', function () { const d = new Date(); d.setDate(d.getDate() - 1); clearSince(d.getTime()); }); document.getElementById('pastHour').addEventListener('click', function () { const d = new Date(); d.setHours(d.getHours() - 1); clearSince(d.getTime()); }); document.getElementById('pastMinute').addEventListener('click', function () { const d = new Date(); d.setMinutes(d.getMinutes() - 1); clearSince(d.getTime()); });style.css 控制弹窗外观,宽度建议 320 到 400 像素之间,太宽会显得突兀:
body { width: 360px; padding: 16px; background-color: #f5f5f5; font-family: Arial, "Microsoft YaHei", sans-serif; font-size: 14px; color: #333; } h1 { font-size: 20px; text-align: center; margin: 8px 0 16px; } button { display: block; width: 100%; margin-bottom: 8px; padding: 10px; border: none; border-radius: 5px; background-color: #4CAF50; color: #fff; cursor: pointer; } button:hover, button:active { background-color: #333; } #lastCleared { font-weight: bold; margin-top: 12px; text-align: center; }background.js 在 V3 里是 service worker,用来处理安装事件和消息,最小可用版本如下:
chrome.runtime.onInstalled.addListener(function () { console.log('Cache Cleaner 已安装'); }); chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { console.log('收到消息:', request); sendResponse({ status: 'ok' }); });如果你还想加 content script(比如在网页里注入一个按钮),需要在 manifest 里加content_scripts字段,指定matches和js文件。但注意,content script 运行在网页上下文,不能直接调chrome.browsingData,需要通过chrome.runtime.sendMessage转发给 background 处理。这个边界一定要搞清楚,否则你会遇到「权限明明声明了却调不通」的问题。
4. 本地加载验证与成功结果确认
代码写完后,先别急着上架,本地加载验证是最关键的一步。打开 Chrome,地址栏输入chrome://extensions/,回车。右上角有个「开发者模式」开关,打开它。这时左上角会出现三个按钮:「加载已解压的扩展程序」「打包扩展程序」「更新」。点「加载已解压的扩展程序」,选中你那个cache-cleaner文件夹。
如果一切正常,你会看到扩展卡片出现在页面上,显示名称「Cache Cleaner」、版本 1.0.0,还有一个「错误」按钮(如果没错误它是灰色的)。这时候点浏览器工具栏的拼图图标,找到 Cache Cleaner,点它,应该弹出你写的 popup 界面,六个按钮整整齐齐。
点「过去一小时」,如果 popup 底部出现「已成功清除缓存 2025年X月X日 XX:XX」,说明整条链路通了。你还可以打开chrome://extensions/里这个扩展的「Service Worker」链接,会弹出一个 DevTools 窗口,能看到 background.js 里console.log('Cache Cleaner 已安装')的输出。
如果加载时报错,最常见的是这几种。第一种,manifest 里图标路径写错,Chrome 会提示「Could not load icon 'icons/icon-16.png'」,解决方法是确认文件夹里真有这些文件,或者干脆先删掉default_icon和icons字段,用默认图标跑通再说。第二种,popup 点开是空白,多半是 popup.js 里有语法错误,右键 popup 界面选「检查」,在 Console 里能看到具体报错行。第三种,点按钮没反应,检查 manifest 的permissions里有没有browsingData,漏了它chrome.browsingData就是 undefined。
验证通过后,你可以用 TaoToken 的模型对话功能,把报错信息贴进去问「这个 Chrome 扩展报错是什么意思,怎么改」,比搜索引擎快很多。模型对话入口在 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 。如果你在写更复杂的扩展、需要模型持续帮你改代码,用 Coding Plan 会更顺手。
上架流程也顺带说清楚。本地验证没问题后,回到chrome://extensions/,点「打包扩展程序」,选择你的文件夹,Chrome 会生成一个.crx文件和一个.pem私钥文件。私钥一定要保存好,后续更新版本要用同一个私钥。然后去 Chrome 开发者后台注册开发者账号(需要一次性费用),创建新项目,上传打包好的 zip(注意是 zip 不是 crx),填写商店信息、截图、隐私说明,提交审核。审核通常几天到两周不等。第一次上架最容易卡在隐私政策说明上,因为你的扩展申请了browsingData权限,必须在说明里讲清楚「为什么需要这个权限、数据怎么处理」。
5. 本篇常见错误排查:401、权限、choices 报错
这一节把开发过程中真实会撞到的报错集中过一遍,每个都给你定位方法和修复动作。
第一个,调用 TaoToken 时返回 401。完整报错通常是{"error":{"message":"Invalid API key","type":"invalid_request_error"}}。原因有三个:Key 复制时多了空格、请求头没写Authorization: Bearer、或者 Key 已经被删除。修复方法是重新到 API Keys 页面生成一个,复制时注意别带上换行。请求头格式必须是Authorization: Bearer sk-xxxx,Bearer 和 Key 之间有一个空格。
第二个,返回local proxy failed或连接超时。这通常是 Base URL 写错。正确写法是https://taotoken.net/api,如果你在代码里又拼了/v1/chat/completions,完整路径就是https://taotoken.net/api/v1/chat/completions。有些人会写成https://taotoken.net/api/v1作为 Base URL,然后再拼/v1/chat/completions,变成/v1/v1/重复,就会失败。检查你的配置里 Base URL 到底填到哪一层。
第三个,解析响应时报reading 'choices'或Cannot read properties of undefined (reading 'choices')。这说明你拿到的响应体里没有choices字段,多半是请求本身失败了,但你的代码直接去读response.choices[0]。正确做法是先判断response.error是否存在,再读choices。比如:
const data = await res.json(); if (data.error) { console.error('请求失败:', data.error.message); return; } const content = data.choices[0].message.content;第四个,Chrome 扩展加载时报Manifest version 2 is deprecated。这是你抄了老教程的 manifest。把manifest_version改成 3,同时把background.scripts数组改成background.service_worker单文件,browser_action改成action。这三个字段是 V2 到 V3 最常踩的迁移点。
第五个,OAuth 相关报错,比如你在扩展里接了某个需要登录的服务,报OAuth2 not granted or revoked。Chrome 扩展做 OAuth 要用chrome.identityAPI,并且 manifest 里要声明identity权限和oauth2配置块。如果你只是本地测试,建议先用 API Key 方式,别一上来就搞 OAuth,复杂度高很多。
第六个,popup 里调chrome.tabs报 undefined。这是因为 popup 上下文里chrome.tabs需要tabs权限,而且部分 API 只能在 background 里调。解决办法是把逻辑挪到 background.js,popup 通过chrome.runtime.sendMessage发消息触发。
如果你用的是 Cline MCP 或 Claude Code 这类工具来辅助写扩展,配置时同样要写全三件套:Base URL 填https://taotoken.net/api,API Key 填你生成的,Model ID 填对应模型标识。Claude Code 的接入文档在 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=claudecode&utm_campaign=rewrite ,Codex 的 auth.json 配置也类似,把 base_url 和 api_key 填对即可。任何一件套缺失,都会导致调用失败。
6. 把扩展做成产品:从原型到上架的收尾建议
跑通原型只是第一步,真正让扩展有价值的是把它做成解决具体问题的工具。我拿「翻译扩展」举个例子,你可以照着这个思路扩展你的清缓存工具。一个实用的翻译扩展通常需要:划词翻译、整页翻译、多翻译服务切换。技术上,划词用 content script 监听mouseup事件拿选中文本,整页翻译遍历 DOM 节点替换文本,多服务切换则在 background 里根据配置调不同 API。
这里有个架构上的关键决策:content script 负责「读页面、改页面」,background 负责「调 API、管配置」,popup 负责「给用户操作界面」。三者通过消息通信解耦。你写清缓存扩展时其实已经用到了这个模式,只是简单一些。把这个模式吃透,你就能做更复杂的扩展。
关于用 AI 辅助开发,我的经验是:把 AI 当成一个「知道很多但需要你给上下文」的结对伙伴。提问时带上你的 manifest 内容、报错信息、期望行为,它给的代码质量会高一个档次。用 TaoToken 统一通道的好处是,你可以在编辑器插件、命令行、网页对话之间共享同一个 Key 和同一套模型配置,不用每换一个工具就重新配一遍。长期做编码和 Agent 场景的话,Coding Plan 的额度模型更适合高频调用,具体可以看 https://taotoken.net/api?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。
最后给一个上架前的自查清单:manifest 版本是 3、权限声明和实际调用一致、图标文件齐全且路径正确、popup 在本地加载无报错、隐私说明写清楚每个权限的用途、版本号遵循语义化(1.0.0 这种)。这六项过了,审核通过率会高很多。扩展开发不难,难的是把每个细节都验证到位,而 AI 能帮你加速的,正是那些你反复查文档的琐碎环节。