# 元字段

Metafields API 用于在前台 JavaScript 中读取元字段，补上 Liquid 做不到的部分：Liquid 在页面渲染时取值，而且只能取当前页面已有的对象，例如商品页上的 `product.metafields`。这两个 endpoint 在浏览器里任意时刻都能调，按对象 id 取值，因此可以读任意商品、专辑或店铺自身的元字段——页面加载完之后读、在拿不到 Liquid 上下文的脚本里读，或者一次请求读多个对象。

元字段是什么、怎么定义，见[元字段概述](/zh-CN/docs/theme/theme-features/custom-data/metafields/overview)；Liquid 侧的读法见[在 Liquid 中使用元字段](/zh-CN/docs/theme/theme-features/custom-data/metafields/using-metafields-in-liquid)。

两个 endpoint 匿名访客都可以调用。

所有 Ajax API 请求应使用[多语言 URL](/zh-CN/docs/theme/references/ajax-api/#locale-aware-urls)，以便为访客提供一致的体验。

## 按对象类型读取元字段列表

```text
GET /{locale}/api/front/metafields/{owner_resource}/list
```

以平铺列表的形式读取某一类对象的元字段。`owner_resource` 取 `shop`、`product`、`product_variant` 或 `collection`。

返回什么由两件事决定：`owner_ids` 传几个，以及是否用 `namespace` 和 `key` 指定字段：

| 想读什么 | `owner_ids` | `namespace` + `key` |
| --- | --- | --- |
| 一条元字段 | 传一个 id | 传一对 |
| 一个对象的全部元字段 | 传一个 id | 不传 |
| 多个对象的同一个字段 | 每个 id 重复传一次 | 传一对 |
| 多个对象的全部元字段 | 每个 id 重复传一次 | 不传 |

`limit` 和 `page` 只负责翻页，不影响选出哪些元字段。

### 请求示例

**请求示例**

```javascript
const params = new URLSearchParams({
  namespace: 'custom',
  key: 'manual_test',
  limit: '10'
});
params.append('owner_ids', '14a309ec-b966-4cf9-a9bb-33baa821fdbc');
params.append('owner_ids', 'f934ce3e-02c2-4e97-8412-19221eedbffd');

fetch(window.SHOPLAZZA.routes.root + '/api/front/metafields/product/list?' + params)
  .then((response) => response.json())
  .then((data) => {
    // do something...
  });
```

### 请求参数

- `owner_resource` string

  对象类型：`shop`、`product`、`product_variant` 或 `collection`。
- `owner_ids` string

  要读取的对象 id。重复传该参数可一次读多个对象。`shop` 时传店铺 id。
- `namespace` string

  只返回该命名空间下的元字段。
- `key` string

  只返回该 key 的元字段，与 `namespace` 一起使用。
- `definition_id` string

  只返回由该元字段定义创建的元字段。与批量查询接口 `definition_ids` 用的是同一种 id。
- `limit` number

  返回的元字段条数。
- `page` number

  返回第几页元字段，从 1 开始，传 `0` 按 `1` 处理。

### 响应

`total`、`remain`、`page`、`total_page`、`have_next` 在返回了元字段时也保持为 0 或 false。

**数据结构 | 示例**

- `data` object

  响应数据主体。

  - `metafields` object[]

    元字段列表。

    *   Array \[

    - `id` string

      元字段的 ID。
    - `store_id` number

      对象所属店铺的 ID。
    - `owner_id` string

      元字段所属资源的 ID。
    - `owner_resource` string

      元字段所属的资源类型，例如 `product`。
    - `namespace` string

      元字段所属的命名空间。
    - `key` string

      元字段的键名，在命名空间内唯一。
    - `value` string

      元字段的值，数据类型由 `type` 决定。
    - `type` string

      元字段的值类型，例如 `single_line_text_field`。
    - `description` string

      元字段的描述。
    - `created_at` string

      创建时间（ISO-8601 格式）。
    - `updated_at` string

      最后更新时间（ISO-8601 格式）。
    *   \]
  - `total` number

    匹配到的元字段总数。
  - `remain` number
  - `page` number

    当前页码。
  - `total_page` number

    总页数。
  - `have_next` boolean

    是否还有下一页。
- `msg` string

  响应返回的结果说明。
- `state` number

  请求结果标识，`success` 或 `0` 表示请求成功。

```json
{
  "data": {
    "metafields": [
      {
        "id": "676328500579297095",
        "store_id": 2446407,
        "owner_id": "14a309ec-b966-4cf9-a9bb-33baa821fdbc",
        "owner_resource": "product",
        "namespace": "custom",
        "key": "manual_test",
        "value": "222222222",
        "type": "single_line_text_field",
        "description": "",
        "created_at": "2026-08-10T07:28:25Z",
        "updated_at": "2026-08-10T07:28:25Z"
      },
      {
        "id": "686766315775468871",
        "store_id": 2446407,
        "owner_id": "f934ce3e-02c2-4e97-8412-19221eedbffd",
        "owner_resource": "product",
        "namespace": "custom",
        "key": "manual_test",
        "value": "333333333",
        "type": "single_line_text_field",
        "description": "",
        "created_at": "2026-09-08T02:44:09Z",
        "updated_at": "2026-09-08T02:44:09Z"
      }
    ],
    "total": 0,
    "remain": 0,
    "page": 0,
    "total_page": 0,
    "have_next": false
  },
  "msg": "请求成功",
  "state": 0
}
```

**读取单条元字段**

前台没有按元字段 id 取单条的 endpoint。要读取任意对象的某一条元字段，调用本 endpoint，路径里放该对象的 `owner_resource`，`owner_ids` 只传一个对象 id，再加上这条元字段的 `namespace` 和 `key`，`data.metafields` 里就只有这一条。对比上面的请求示例：那里传了两个 `owner_ids`，读的是两个商品的同一个字段。这里读的是一个商品的一条元字段，其他对象把路径里的 `product` 换成 `shop`、`product_variant` 或 `collection` 即可：

```javascript
const params = new URLSearchParams({
  owner_ids: '14a309ec-b966-4cf9-a9bb-33baa821fdbc',
  namespace: 'custom',
  key: 'manual_test'
});

fetch(window.SHOPLAZZA.routes.root + '/api/front/metafields/product/list?' + params)
  .then((response) => response.json())
  .then((data) => {
    const metafield = data.data.metafields[0];
    console.log(metafield ? metafield.value : 'not set');
  });
```

下面的批量 endpoint 在 `unique_keys` 只传一对 `namespace` 和 `key` 时效果相同。前台不能新建或修改元字段，这类操作走 [Metafield REST API](/zh-CN/api/metafield)。

## 批量按对象查询元字段

```text
POST /{locale}/api/front/v2/metafields/{owner_resource}/query
```

一次请求读取多个对象的元字段，并按对象分组返回。`owner_resource` 支持 `shop`、`product` 和 `collection`；传其他取值会返回 `422`，响应体为 `{"errors":["unsupported owner_resource"]}`。

筛选元字段有两种方式：用 `unique_keys` 按命名空间和 key 筛，或用 `definition_ids` 按定义筛。两者必须且只能传一个：都不传或都传都会返回 `422`，响应体为 `{"errors":["exactly one of unique_keys or definition_ids is required"]}`。

`owner_resource` 为 `shop` 时可以不传 `owner_ids`，因为店铺是唯一可能的 owner。

### 请求示例

**请求示例**

```javascript
const data = {
  owner_ids: ['14a309ec-b966-4cf9-a9bb-33baa821fdbc'],
  unique_keys: [
    { namespace: 'custom', key: 'manual_test' }
  ]
};

fetch(window.SHOPLAZZA.routes.root + '/api/front/v2/metafields/product/query', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json'
  },
  body: JSON.stringify(data)
})
  .then((response) => response.json())
  .then((data) => {
    // do something...
  });
```

### 请求参数

**数据结构 | 示例**

- `owner_resource` string

  对象类型：`shop`、`product` 或 `collection`。
- `owner_ids` string[]

  要读取的对象 id 列表。`owner_resource` 为 `shop` 时可省略。
- `unique_keys` object[]

  要读取的元字段，每项是带 `namespace` 和 `key` 的对象。

  *   Array \[

  - `namespace` string
  - `key` string
  *   \]
- `definition_ids` string[]

  要读取的元字段定义 id 列表。与 `unique_keys` 二选一，不能同时传。

```json
{
  "owner_ids": [
    "14a309ec-b966-4cf9-a9bb-33baa821fdbc"
  ],
  "unique_keys": [
    {
      "namespace": "custom",
      "key": "manual_test"
    }
  ]
}
```

### 响应

**数据结构 | 示例**

- `data` object

  响应数据主体。

  - `owner_metafields` object[]

    按所属资源分组的元字段。

    *   Array \[

    - `owner_id` string

      元字段所属资源的 ID。
    - `metafields` object[]

      元字段列表。

      *   Array \[

      - `id` string

        元字段的 ID。
      - `store_id` number

        对象所属店铺的 ID。
      - `owner_id` string

        元字段所属资源的 ID。
      - `owner_resource` string

        元字段所属的资源类型，例如 `product`。
      - `namespace` string

        元字段所属的命名空间。
      - `key` string

        元字段的键名，在命名空间内唯一。
      - `value` string

        元字段的值，数据类型由 `type` 决定。
      - `type` string

        元字段的值类型，例如 `single_line_text_field`。
      - `description` string

        元字段的描述。
      - `created_at` string

        创建时间（ISO-8601 格式）。
      - `updated_at` string

        最后更新时间（ISO-8601 格式）。
      - `definition_id` string

        元字段关联的定义 ID。
      *   \]
    *   \]
- `msg` string

  响应返回的结果说明。
- `state` number

  请求结果标识，`success` 或 `0` 表示请求成功。

```json
{
  "data": {
    "owner_metafields": [
      {
        "owner_id": "14a309ec-b966-4cf9-a9bb-33baa821fdbc",
        "metafields": [
          {
            "id": "676328500579297095",
            "store_id": 2446407,
            "owner_id": "14a309ec-b966-4cf9-a9bb-33baa821fdbc",
            "owner_resource": "product",
            "namespace": "custom",
            "key": "manual_test",
            "value": "222222222",
            "type": "single_line_text_field",
            "description": "",
            "created_at": "2026-08-10T07:28:25Z",
            "updated_at": "2026-08-10T07:28:25Z",
            "definition_id": "676319747943433287"
          }
        ]
      }
    ]
  },
  "msg": "请求成功",
  "state": 0
}
```
