# categories 清单成员

> categories 成员是一个小写字符串数组，告诉应用商店和目录把 Web 应用归入哪些分区；浏览器会解析它，但不会据此渲染任何内容。

`categories` 是一个字符串数组，列出 Web 应用所属的商店分区，例如 `"productivity"` 或
`"games"`。W3C Application Information 注册表把它定义为给目录和商店的提示；浏览器引擎会解析
该成员，但应用的安装、启动与渲染方式不会因它发生任何变化。

由于引擎内部没有任何环节消费它，browser-compat-data 没有为任何浏览器记录支持版本（BCD
`html.manifest.categories`）。Chromium 的清单解析器（`manifest_parser.cc`）接受该成员并把每个
字符串转为小写；Firefox 157 与 Safari 27 解析后直接丢弃。真正的消费者是分发渠道：PWABuilder
这类商店打包工具在生成 Microsoft Store 或 Play Store 封装的列表元数据时会读取这个数组。

## 成员

- **类型**：字符串数组。注册表公布了一组已知值：`business`、`education`、`entertainment`、
  `finance`、`fitness`、`food`、`games`、`government`、`health`、`kids`、`lifestyle`、
  `magazines`、`medical`、`music`、`navigation`、`news`、`personalization`、`photo`、
  `politics`、`productivity`、`security`、`shopping`、`social`、`sports`、`travel`、
  `utilities` 与 `weather`。其他字符串也合法；商店会把它们映射到自己的分类体系或忽略。
- **默认值**：空数组。找不到可用分类的商店会自行归类。
- **示例值**：`["food", "health", "lifestyle"]`。

值的比较不区分大小写：注册表要求作者写小写，Chromium 解析器在存储前也会把每一项转为小写。
值不是数组时，解析器用通用的类型提示 `property 'categories' ignored, type array expected.`
丢弃它，清单的其余部分不受影响。

该成员在两个方向上都只是建议。商店可以把应用列在清单没有提到的分区下；清单列出的分类也不会
出现在已安装应用或浏览器安装对话框的任何位置。

:::observed
Chrome 155（macOS 26，英文界面）的 DevTools > Application > Manifest 面板没有 `categories`
这一行。它的 **Identity** 一节只列出 Name、Short name、Description、Computed App Id，
**Presentation** 一节只列出 Start URL、Theme color、Background color、Orientation 与 Display。
面板唯一会对该成员做出反应的地方是 **Errors and warnings**：写成 `"categories": "games"`
（字符串而不是数组）时，那里显示 `property 'categories' ignored, type array expected.`。
:::

## 示例

该成员只是声明，没有运行时表面，所以示例覆盖的是怎么写、以及怎么检查上线的值。

### 为一个应用声明多个商店分区

一款膳食规划应用适合不止一个分区。列出三个分类，支持多分类列表的商店可以把它放进每一个分区；
只支持单一分类的商店取它认得的第一个。

```json
{
  "name": "Meal Planner",
  "short_name": "Meals",
  "start_url": "/",
  "display": "standalone",
  "categories": ["food", "health", "lifestyle"]
}
```

把最贴切的分类放在第一位：必须选出唯一主分类的打包工具没有别的依据可用。

### 在构建步骤中校验上线的值

没有任何 API 能报告浏览器或商店是否采用了该成员，所以有用的检查是在构建或测试阶段对将要部署的
清单做断言。下面的函数按 Chromium 解析器的方式归一化数组，并标出注册表已知列表之外的值。

```js
const KNOWN = new Set([
  'business', 'education', 'entertainment', 'finance', 'fitness', 'food', 'games',
  'government', 'health', 'kids', 'lifestyle', 'magazines', 'medical', 'music',
  'navigation', 'news', 'personalization', 'photo', 'politics', 'productivity',
  'security', 'shopping', 'social', 'sports', 'travel', 'utilities', 'weather',
]);

export function checkCategories(manifest) {
  const raw = manifest.categories;
  if (raw === undefined) return { declared: [], unknown: [] }; // 可选成员
  if (!Array.isArray(raw)) throw new Error("categories must be an array of strings");
  const declared = raw.map((c) => String(c).toLowerCase());
  const unknown = declared.filter((c) => !KNOWN.has(c));
  return { declared, unknown };
}
```

缺少该成员的清单是合法的，所以函数返回空列表而不是失败；未知值只报告、不拒绝，因为商店接受
自由格式的字符串。

### 从页面自己的清单里读取该值

对于镜像商店元数据的设置页或"关于"页，可以抓取文档链接的清单并读取数组。回退分支处理没有
清单链接的页面。

```js
async function declaredCategories() {
  const link = document.querySelector('link[rel="manifest"]');
  if (!link) return []; // 该文档没有链接清单
  const manifest = await fetch(link.href).then((r) => r.json());
  return Array.isArray(manifest.categories) ? manifest.categories : [];
}
```

它只能确认站点声明了什么，不能说明商店是否真的把应用列在了那些分区下。

## 另请参阅

- [description 清单成员](/zh/reference/manifest/description/)
- [screenshots 清单成员](/zh/reference/manifest/screenshots/)
- [通过应用商店分发 PWA](/zh/guides/app-store-distribution/)
- [Web App Manifest - Application Information: categories member](https://w3c.github.io/manifest-app-info/#categories-member)（w3c.github.io）
- [categories](https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Manifest/Reference/categories)（developer.mozilla.org）