首页 / WXT 浏览器扩展框架教程 / wxt.config.ts 与 manifest 详解

WXT 浏览器扩展框架教程

wxt.config.ts 与 manifest 详解

本教程共 45 篇 · 第 25 篇 · 更新于 2026-08-13 · 约 4 分钟阅读

WXTwxt.config.tsmanifest清单配置MV3MV2跨浏览器

本节目标:看懂 WXT 的总配置入口 wxt.config.ts,弄清清单(manifest.json)是怎么拼出来的,学会逐字段配置清单,并按浏览器分支返回不同内容。

前几章里,你已经在每个项目里见过 wxt.config.ts。它位于项目根目录,导出 defineConfig(...) 的返回值,是 WXT 的总配置入口。manifestvitehooksmodulesaliasimports 这些配置都写在它里面。这一章先把最核心的 manifest 字段讲透,其余字段在后续章节展开。

清单不是手写的,是拼出来的

原生开发扩展时,你要亲手维护 manifest.json。WXT 的源码目录里没有这个文件,清单(manifest)由多个来源合并生成:

  1. wxt.config.ts 里的全局 manifest 配置
  2. 入口点文件内定义的选项,如 §07 所述的 manifest 选项
  3. WXT 模块(§31)对清单的修改
  4. 构建钩子(§30)对清单的修改

执行 wxt build 后,合并结果输出到 .output/{目标}/manifest.json。也就是说,你写的是「生成清单的规则」,而不是清单本身。

全局 manifest 配置

wxt.config.ts 里加一个 manifest 键,就能往清单里塞任意属性:

export default defineConfig({
  manifest: {
    // 手动补充的字段都放这里
  },
});

manifest 也可以是函数,用 JavaScript 按目标浏览器、模式等条件动态生成:

export default defineConfig({
  manifest: ({ browser, manifestVersion, mode, command }) => {
    return {
      // ...
    };
  },
});

函数的四个参数分别表示:目标浏览器、目标清单版本、构建模式、执行的命令。这是实现「一套配置,多端输出」的关键,后面会用到。

常用字段逐个看

name:不配置时,默认取 package.jsonname 字段。

version 与 version_name:都来自 package.jsonversionversion_name 原样保留;version 会被清理掉非法后缀。比如版本号写 "1.3.0-alpha2",产物清单里就是 version: "1.3.0"version_name: "1.3.0-alpha2"package.json 没写版本时,默认 "0.0.0"

icons:WXT 会自动扫描 public/ 目录,按 icon-16.pngicon-48.png 这类命名发现图标(支持 16/24/32/48/96/128 等尺寸)。不想改文件名时,也可以手动指定:

export default defineConfig({
  manifest: {
    icons: {
      16: '/extension-icon-16.png',
      48: '/extension-icon-48.png',
      128: '/extension-icon-128.png',
    },
  },
});

想要「一张原图自动生成所有尺寸」,用 @wxt-dev/auto-icons 模块,见 §32。

theme_icons(Firefox 专属):Firefox 支持按浏览器主题在亮色/暗色图标间切换。目标为 Firefox 时,WXT 会自动发现 public/ 里配对的 icon-light-{size}.pngicon-dark-{size}.png,只有亮暗两个文件都齐全的尺寸才会进入 theme_icons。当前只识别 .png。如果你在配置里手动写了 action.theme_icons,WXT 不会覆盖它。

permissions:绝大多数权限要手动声明。只有两种自动添加的情况:开发模式下自动加 tabsscripting(热重载需要);存在 sidepanel 入口时自动加 sidepanel 权限。权限的详细讲法见 §21,这里只提醒一句:不同浏览器支持的权限不同,最好用函数分支:

export default defineConfig({
  manifest: ({ browser }) => ({
    permissions:
      browser === 'chrome'
        ? ['storage', 'favicon', 'declarativeNetRequest']
        : ['storage', 'webRequest'],
  }),
});

host_permissions:写法与 permissions 类似。如果同时面向 MV2 和 MV3,注意两个版本的 host 权限规则不同,建议按 manifestVersion 分支:

export default defineConfig({
  manifest: ({ manifestVersion }) => ({
    host_permissions: manifestVersion === 2 ? ['https://a.com/*'] : ['https://b.com/*'],
  }),
});

default_locale:做国际化时使用,配合 __MSG_xxx__ 占位符,详见 §19:

export default defineConfig({
  manifest: {
    name: '__MSG_extName__',
    description: '__MSG_extDescription__',
    default_locale: 'en',
  },
});

action:MV3 的 action 在 MV2 下会自动回退成 browser_action。有 popup 入口时,WXT 会自动生成带 popup 的 action(§08)。如果你只想用 activeTab 权限或 browser.action.onClicked 事件、不想要弹窗,做法是:删掉 popup 入口,再在清单里加一个空 action: {}。想用 MV2 的 page_action,则在 HTML 的 <head> 里加 <meta name="manifest.type" content="page_action" />,或同时声明 actionpage_action 两个键。

MV2 与 MV3:写一份,跑两版

WXT 的兼容策略很省心:能写 MV3 格式就写 MV3 格式,目标换成 MV2 时,WXT 自动转换。比如下面这份配置:

export default defineConfig({
  manifest: {
    action: {
      default_title: 'Some Title',
    },
    web_accessible_resources: [
      {
        matches: ['*://*.google.com/*'],
        resources: ['icon/*.png'],
      },
    ],
  },
});

构建出来的两个版本分别是:

{
  "manifest_version": 2,
  "browser_action": {
    "default_title": "Some Title"
  },
  "web_accessible_resources": ["icon/*.png"]
}
{
  "manifest_version": 3,
  "action": {
    "default_title": "Some Title"
  },
  "web_accessible_resources": [
    {
      "matches": ["*://*.google.com/*"],
      "resources": ["icon/*.png"]
    }
  ]
}

只属于某个清单版本的属性,面向另一版本构建时会被自动剥掉,不用你操心。

按浏览器分支返回清单

真实项目里,不同浏览器的要求差异很大。看一个生产级项目(mkext)的做法:

export default defineConfig({
  manifest: ({ browser }) => {
    const isFirefox = browser === 'firefox';

    const manifest = {
      name: '__MSG_extensionName__',
      default_locale: 'en',
      key: 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...', // Chromium 专用
      permissions: ['identity', 'storage', 'tabs'],
      oauth2: {
        client_id: import.meta.env.VITE_GOOGLE_EXTENSION_CLIENT_ID,
        scopes: ['openid', 'email', 'profile'],
      }, // Chromium 专用
      minimum_chrome_version: isFirefox ? undefined : '114',
      browser_specific_settings: isFirefox
        ? { gecko: { id: 'mkext-template@mkfast.dev' } }
        : undefined,
    };

    if (isFirefox) {
      const { key, oauth2, ...firefoxManifest } = manifest;
      return firefoxManifest; // 剥离 Chromium 专有字段
    }
    return manifest;
  },
});

这里有两个值得记住的点:

  • minimum_chrome_version:给 Chromium 系浏览器声明最低版本。mkext 写 "114",因为它的 sidepanel 入口需要 Chrome 114,声明下限可以避免老版本浏览器装上一个半残的扩展。
  • browser_specific_settings.gecko:Firefox 用它声明扩展 ID 等信息。keyoauth2 是 Chromium 专有:Firefox 靠 gecko.id 识别扩展,没有 identity.getAuthToken(),AMO 审核还会把未知清单字段当问题标记。所以 mkext 在 Firefox 分支里把这两个字段解构剥离,顺带避免泄露 OAuth client ID。
Note

matches 相关规则在 §11 内容脚本一章有详细说明;权限体系整体讲解在 §21。本章只负责「配置怎么写」。

小结

  • manifest 由 WXT 从多个来源生成:入口文件选项、public/ 图标、config 里的 manifest 函数。
  • 常用字段逐个看:name / version / icons / permissions / action,跨浏览器差异用 manifest 函数分支。
  • 图标、权限等能自动发现和补全的,别手写。