开发指南

从第一个脚本到上架市场:元数据怎么写、能用哪些权限、常见坑怎么排。兼容 Tampermonkey 的元数据格式,已有脚本通常无需改动。

五分钟上手

脚本就是一段跑在网页里的 JavaScript,头部加一小块注释告诉平台「它叫什么、在哪些站点跑、要用什么权限」。整个过程不涉及编译和打包。

STEP 1

想清楚改什么

只解决一个问题:去掉广告位、加个下载按钮、记住倍速。范围越小越容易过审。

STEP 2

写元数据头

至少 @name、@version、@match 三项,写在 ==UserScript== 注释里。

STEP 3

写逻辑

操作 DOM、加样式、读写本地存储。需要跨域请求或通知时再申请对应权限。

STEP 4

本地试跑

扩展面板「导入本地脚本」拖入 .user.js,刷新目标页面看效果。

STEP 5

粘贴发布

打开发布页粘贴代码,元数据自动解析,填分类即可提交审核。

最小可用脚本长这样:一段注释头 + 一个立即执行函数。不需要构建工具,也不需要 document.write。

元数据块

脚本开头用 ==UserScript== / ==/UserScript== 包裹一段注释,平台会把它解析成市场信息与运行配置。字段名大小写不敏感,一行一个。

字段必填说明
@name是脚本名称,展示在列表与安装确认页
@version是语义化版本(如 1.2.0)。更新必须递增,否则用户侧不会触发更新
@description推荐一句话说明,列表页会截断展示
@match / @include是注入规则,可写多条;两者都支持通配与正则
@exclude否排除规则,优先级高于 @match
@grant否申请的权限清单,一行一个;写 none 表示不需要任何特权
@connect否允许跨域访问的域名白名单;声明后未列出的域名会被拦截
@run-at否document-start / document-end / document-idle(默认)
@require否远程依赖脚本地址,安装时下载并内联执行,可写多条
@resource否静态资源(图片、样式),配合 GM_getResourceURL 取用
@author否作者署名,默认取发布账号
@namespace否区分同名脚本,建议用你的域名或仓库地址
@inject-into / @sandbox否强制注入位置:page(页面主世界)或 content(扩展沙箱)
@updateURL / @downloadURL否自建更新源时使用;本站发布的脚本由平台托管更新,不需要写
@noframes否加上后脚本只在顶层页面运行,不注入任何 iframe
@license推荐开源协议(如 MIT),展示在详情页「许可」栏;不写视为保留所有权利
@homepage / @supportURL否脚本主页与问题反馈地址,展示在详情页「脚本链接」卡;必须是 http(s) 链接
@antifeature视情况脚本包含广告、返佣跳转等不欢迎的功能时必须声明:// @antifeature ads 脚本拥有广告。瞒报会被下架,声明后详情页会展示黄条提醒用户

@match 与作用范围

采用 scheme://host/path 三段式通配,* 表示任意段。下面几条覆盖绝大多数场景:

写法匹配范围
*://*/*所有 http/https 页面(审核重点,非必要别用)
*://*.bilibili.com/*bilibili.com 及其所有子域
https://github.com/*/*仅 GitHub 仓库页
*://item.jd.com/*京东商品详情页
@include /正则/用正则匹配,例如 /^https:\/\/example\.com\/(a|b)\//
@exclude *://*/login*排除登录页,避免和登录流程打架
提示:规则越窄越好。只匹配需要的页面既能减少性能开销,也更容易过审;*://*/* 加 @grant 特权会被人工复核。

单页应用(SPA)切换路由时不会重新加载页面,平台会在 URL 变化后按新地址重新匹配并注入一次,脚本里不需要自己监听 history。

支持的权限

权限由 @grant 声明。平台会按声明内容在安装页展示「这个脚本能做什么」,用户确认后才启用。常见组合:

场景建议 @grant
纯改样式、加按钮,不动存储与网络none(或只写 GM_addStyle)
记住用户偏好、保存开关状态GM_getValue GM_setValue GM_deleteValue
调用第三方接口补数据GM_xmlhttpRequest + @connect 目标域名
加一个「设置」入口GM_registerMenuCommand
导出/下载文件GM_download
注意:需要读取或写入 Cookie 的脚本必须声明 GM_cookie,并且会在审核阶段被重点检查。读取登录凭据、把 Cookie 外发属于禁止项,见发布规范。

GM API 清单

下表是当前扩展完整支持的 API。它们按脚本隔离,不会污染页面全局;没声明 @grant 的脚本也能调用,但会在安装确认页被视为「未申报权限」,影响过审。

样式与元素

API用途
GM_addStyle(css)向页面 head 注入样式,返回 style 元素。改外观首选它,别直接改行内样式
GM_addElement(tag, attrs)创建并插入元素,{textContent, src, ...}

本地存储

API用途
GM_getValue(key, default)读取脚本私有存储,支持对象
GM_setValue(key, value)写入,值会 JSON 序列化
GM_deleteValue(key)删除单个键
GM_listValues()枚举该脚本的全部键
GM_addValueChangeListener(key, fn)监听值变化,多标签页同步时 remote=true
GM_removeValueChangeListener(id)取消监听

网络与下载

API用途
GM_xmlhttpRequest(opts)跨域请求,支持 method / headers / data / responseType / onload / onerror / onprogress / timeout / abort()
GM_download(url, name)触发下载并命名文件,可传 {url, name, onload, onerror}
GM_getResourceText(name) / GM_getResourceURL(name)读取 @resource 声明过的文本资源或 data URL

界面与系统

API用途
GM_notification(opts)桌面通知:{title, text, timeout, onclick}
GM_setClipboard(text, type)写入剪贴板,建议由点击事件触发
GM_openInTab(url, opts)打开新标签页,{active, insert, setParent}
GM_registerMenuCommand(label, fn, accessKey)在当前页面注册命令,显示为右下角浮动菜单 / 浏览器右键菜单
GM_unregisterMenuCommand(id)移除命令
GM_log(...)输出到扩展日志,方便远程排查
GM_info脚本自身信息:名称、版本、运行环境
GM_cookie.list/set/delete读写当前域 Cookie,需显式声明
GM_getTab/saveTab/getTabs按标签页共享临时数据
unsafeWindow页面真实 window。仅当脚本以「页面主世界」方式运行时可用,访问页面框架内部对象时需要

运行机制与沙箱

  • 两种运行位置:不依赖特权 API 的脚本直接注入页面主世界,速度最快,也能拿到 unsafeWindow;声明了特权的脚本跑在扩展沙箱里,GM API 以参数形式注入,页面拿不到你的内部状态。
  • 权限声明的作用:决定运行位置与安装页展示的权限清单。想强制指定位置可用 @inject-into page|content。
  • 存储隔离:每个脚本的 GM_getValue 空间互相独立,卸载脚本时可选择一并清除。
  • 失败隔离:单个脚本抛错只影响它自己,页面和其他脚本继续运行;错误会写进扩展日志。
  • 执行时机:document-start 在 DOM 建好前执行,适合改 fetch/XHR;改页面内容用默认的 document-idle 最稳。

完整示例

入门:给页面加一条提示

// ==UserScript==
// @name         页面提示条
// @namespace    https://example.com/
// @version      1.0.0
// @description  在页面左上角显示一条提示
// @match        *://example.com/*
// @grant        GM_addStyle
// @run-at       document-idle
// ==/UserScript==

(function () {
  'use strict';
  if (document.getElementById('tip')) return; // 幂等:防止重复注入

  GM_addStyle('#tip{position:fixed;left:16px;top:16px;padding:8px 14px;' +
    'border-radius:9px;background:#12161d;color:#c6f24e;font:13px sans-serif;z-index:99999}');

  const el = document.createElement('div');
  el.id = 'tip';
  el.textContent = '脚本已注入';
  document.documentElement.appendChild(el);
})();

进阶:开关 + 存储 + 跨域请求

// ==UserScript==
// @name         站点增强(示例)
// @version      1.1.0
// @description  记忆开关状态,并把页面标题上报到自己的接口
// @match        *://example.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest
// @connect      api.example.com
// @run-at       document-idle
// ==/UserScript==

(function () {
  'use strict';
  const KEY = 'enabled';
  const on = GM_getValue(KEY, true);

  function apply(state) {
    document.documentElement.toggleAttribute('data-enhance', state);
  }
  apply(on);

  GM_registerMenuCommand(on ? '关闭增强' : '开启增强', () => {
    const next = !GM_getValue(KEY, true);
    GM_setValue(KEY, next);
    apply(next);
    location.reload();
  });

  // 跨域请求:域名必须在 @connect 里声明,否则会被拦下
  GM_xmlhttpRequest({
    method: 'POST',
    url: 'https://api.example.com/stat',
    headers: { 'Content-Type': 'application/json' },
    data: JSON.stringify({ title: document.title, version: GM_info.script.version }),
    onload: (res) => console.log('上报成功', res.status),
    onerror: () => console.warn('上报失败,不影响主流程'),
  });
})();

粘贴到发布页后,元数据会被自动解析出来,填好名称、分类并上传脚本图标即可提交。

写脚本的 8 条提示

  1. 先判断再插入:注入前检查目标节点是否存在,或用 document.getElementById 做幂等保护,避免反复刷新时叠加多层。
  2. 用 GM_addStyle 而不是行内样式:一处集中管理,站点改版时好维护,也不容易和页面自身样式互相覆盖。
  3. 选择器写得「钝」一点:别依赖编译后的类名(如 .css-1x2y3z),优先用语义化的 data-*、aria-label、结构位置。
  4. 等元素出现再操作:懒加载页面用 MutationObserver 监听,记得节流(100ms 以上),否则主线程会被拖慢。
  5. 版本号必须递增:只要改了代码就必须改 @version(如 1.0.1),否则已安装的用户永远收不到这次更新 —— 发布页和接口都会拦下这种提交。
  6. 请求失败要兜底:网络请求、存储读写都放在 try/catch 里,不能让异常打断页面主流程。
  7. 别把逻辑藏起来:不混淆、不压缩成一行、不用 base64 包主逻辑。审核看不懂的代码会被打回。
  8. 申请最小权限:只用 GM_addStyle 就别顺手写 GM_cookie,权限越少,用户越愿意装。

调试与排错

现象可能原因与处理
脚本完全没跑看 @match 是否匹配当前 URL(含协议、端口);确认脚本在面板里处于开启状态;SPA 页面刷新一次
GM_xxx is not defined沙箱模式下 API 来自声明;补上对应的 @grant;或改到页面主世界运行(@inject-into page)
跨域请求被拦补齐 @connect 目标域名;注意只写域名,不带 https:// 与路径
样式不生效选择器优先级不够或被站点 !important 覆盖;提高优先级或加 !important;CSP 严格的站点建议用 GM_addStyle
改版后彻底失效常见于依赖混淆类名;改用语义选择器,或加多重兜底查询
更新没生效检查 @version 是否递增、是否重新提交审核通过
控制台报 Permission denied脚本以沙箱模式访问页面对象;需要页面上下文时用 unsafeWindow 或改为页面主世界

排查时优先看两处:页面控制台(脚本自身报错)与扩展管理面板的「运行日志」(注入结果、API 调用失败原因)。

发布、定价与分成

  1. 提交:发布页粘贴代码 → 自动解析元数据 → 填分类、截图与详细说明 → 提交审核。
  2. 审核:系统先做静态扫描(Cookie 读取、凭据外传、动态执行等),命中高风险特征转人工。通常 24 小时内出结果。
  3. 更新:在「我的脚本」点该脚本那一行的 更新 进入编辑页,版本号和代码头部的 @version 会自动递增(如 1.0.0 → 1.0.1),改完代码写一句更新说明,提交审核。审核期间线上继续提供上一个已通过的版本,市场排名、安装量和已装用户都不受影响;通过后新版自动替换,已装用户下一次检查更新(Pro 每 12 小时自动,免费版点「检查更新」)即可拿到。
  4. 通知:审核有结果时会往作者邮箱发一封信 —— 通过给出脚本链接,驳回或下架写明原因;更新被驳回时线上旧版本照常提供。站内「我的脚本」里同样能看到状态与原因,邮件只是提醒,没收到可以查一下垃圾箱。
  5. 定价:可设为免费,或按月 / 年 / 永久定价。付费脚本在未购买用户那里只展示说明与权限,不返回源码。
  6. 分成:售出后按平台设定的分成比例计入作者余额,达到最低提现额可申请提现,打款账号在账号中心绑定。
发布前对照脚本发布规范自查一遍:名称别带「破解/免会员」类字样,说明里写清楚网络请求的地址与用途。

云同步怎么工作

云同步不是「后台自动上传」,而是点一次、对齐一次:把本机已安装的脚本清单和云端的上一份备份做双向合并 —— 云端有而本机没有的下载下来,本机有而云端没有的上传上去,两边都有的以本机为准(不会用云端旧代码覆盖你本机改过的版本)。

对象同步什么
市场脚本只备份「装了哪些 + 开关状态」,源码本来就在云端;换设备时按脚本重新拉取当前已过审的版本
本地导入脚本连代码一起备份,换设备能原样还原;单次请求超过 500KB 时降级为只传清单,面板会提示
GM 存储值(GM_setValue)不同步,各设备各自独立(避免把登录态、临时数据带出去)
设置只同步「自动更新」开关

什么时候同步:登录一次会自动同步一次(换新设备时登录完脚本自己就下来了);安装 / 导入 / 删除脚本之后也会自动同步一次,不用再手动点。此外随时可以在扩展里手动点「云同步」(管理面板「已安装」页顶部与「设置与同步」页各有一个入口,双向合并)。自动同步只在已登录且开通云同步时执行,几秒内的多次操作会合并成一次;没有定时后台轮询,也不会上传你没装过的脚本。

换账号不会自动合并:如果本机还装着上一个账号的脚本,登录另一个账号时会跳过自动同步,避免把前一个账号的脚本和删除标记带进新账号。确认要合并时,手动点一次「云同步」即可。

网页端(我的脚本)只显示「上次备份时间 / 脚本数 / 设备数」这类统计,不展示也不写入脚本代码 —— 网页拿不到你本机装了哪些脚本,写进去只会覆盖掉备份。

设备数:每次同步带一个设备标识,服务端按设备去重计数。免费会员不含云同步,Pro 3 台、Team 10 台;达到上限后新设备同步会被拒绝并提示,可到账号设置 → 云同步设备移除不再用的设备(只删该设备的云端备份,不影响本机已装脚本)。

备份保留:每个账号保留最近 5 份快照,用于换机恢复;旧备份没有代码时(早期版本只存清单)会被原样保留而不会被覆盖丢失。

删除会一起同步:删掉一个脚本时并记一条删除标记,下次同步会把云端那条一起删掉——不会出现「删了再点同步又回来」的往复。其他设备同步时也会跟着删掉;想留下就在那台设备上重新安装,装回来会撤销标记。删除标记保留 60 天后自动过期。

常见问题

能用 React / Vue / TypeScript 写吗?

可以,但要先编译成纯 JavaScript 再粘贴。运行时不提供打包与模块解析,import 语句不会被处理;依赖库可用 @require 引入 CDN 版本。

能操作 iframe 里的内容吗?

同源 iframe 可以直接访问;跨域 iframe 受浏览器同源策略限制,需要目标页面自身配合。平台会在所有 frame 中执行匹配到的脚本。

脚本数量有上限吗?

有。免费账号可安装的脚本数量由站点设置决定,达到上限时会提示登录或升级会员;具体额度见会员方案。

云同步是自动的吗?

部分是。登录后会自动同步一次,之后每次安装、导入、删除脚本也会自动同步;其余时刻想立刻对齐(比如刚在另一台设备上改过),在扩展管理面板点「云同步」即可。它做的是双向合并:两边都补充,冲突以本机为准。详见云同步怎么工作。

删掉的脚本,同步后为什么没有回来?

删除会被记成一条标记一起同步:云端对应条目被删掉,其他设备同步时也会跟着删,所以不会再被拉回来。这是 1.9.18 起的行为;更早的版本只做并集、不记删除,才会出现「删了又回来」的往复。要恢复就在扩展里重新安装,装了会撤销这条标记。

能读取页面的登录状态吗?

能读到自己页面上的公开 DOM,但不得读取、外发 Cookie、令牌或密码框内容。此类脚本一律不过审,已上架的会被下架。

脚本被下架了怎么办?

审核记录里会给出原因,按提示修改后重新提交。对处理结果有异议,可在投诉页提交申诉并补充材料。

脚本在别家管理器能用,装到这里没反应?

先按安装页的「三步排查」走一遍:确认网址命中 @match、刷新页面、检查扩展开关。仍然不行,多半是脚本用到了尚未支持的 GM API,可对照API 清单确认;缺的能力欢迎在投诉/反馈入口提给我们。

GitHub、X 这类站点注入不进去?

这些站点启用了严格的 CSP。Chrome 138+ 请在 chrome://extensions → 狂暴鲨 → 详情里打开「允许用户脚本」,扩展会改用浏览器官方脚本接口注入;不开启时会自动回退到普通注入,普通站点不受影响。