# GraphQL API

> Source: https://strapi.nodejs.cn/cms/api/graphql

GraphQL API 允许对内容类型进行查询和修改，并支持过滤、排序和分页。它使用 `documentId` 作为唯一标识符，并提供单数和复数查询，支持关系、媒体字段、组件、动态区域和本地化。

🌐 The GraphQL API allows querying and mutating content-types with filtering, sorting, and pagination. It uses `documentId` as the unique identifier and provides singular and plural queries with support for relations, media fields, components, dynamic zones, and localization.

GraphQL API 允许通过 Strapi 的 [GraphQL 插件](/cms/plugins/graphql) 对 [内容类型](/cms/backend-customization/models#content-types) 执行查询和变更。结果可以被 [过滤](#filters)、[排序](#sorting) 和 [分页](#pagination)。

🌐 The GraphQL API allows performing queries and mutations to interact with the [content-types](/cms/backend-customization/models#content-types) through Strapi's [GraphQL plugin](/cms/plugins/graphql). Results can be [filtered](#filters), [sorted](#sorting) and [paginated](#pagination).

:::prerequisites

要使用 GraphQL API，请安装 [GraphQL](/cms/plugins/graphql) 插件：

🌐 To use the GraphQL API, install the [GraphQL](/cms/plugins/graphql) plugin:

```sh
yarn add @strapi/plugin-graphql
```
```sh
npm install @strapi/plugin-graphql
```

:::

安装完成后，GraphQL playground 可通过 `/graphql` URL 访问，并可用于交互式构建查询和变更操作，以及阅读针对你的内容类型定制的文档：

🌐 Once installed, the GraphQL playground is accessible at the `/graphql` URL and can be used to interactively build your queries and mutations and read documentation tailored to your content-types:

<br/>

GraphQL 插件只公开一个处理所有查询和变更的端点。默认端点是 `/graphql`，并在 [插件配置文件](/cms/plugins/graphql#code-based-configuration) 中定义：

🌐 The GraphQL plugin exposes only one endpoint that handles all queries and mutations. The default endpoint is `/graphql` and is defined in the [plugins configuration file](/cms/plugins/graphql#code-based-configuration):

```js title="/config/plugins.js|ts"
  export default {
    shadowCRUD: true,
    endpoint: '/graphql', // <— single GraphQL endpoint
    subscriptions: false,
    maxLimit: -1,
    apolloServer: {},
    v4CompatibilityMode: process.env.STRAPI_GRAPHQL_V4_COMPATIBILITY_MODE ?? false,
  };
```

:::note No GraphQL API to upload media files

GraphQL API 不支持媒体上传。请使用 [REST API `POST /upload` 端点](/cms/api/rest/upload) 进行所有文件上传，并使用返回的信息在内容类型中链接它。你仍然可以使用 `updateUploadFile` 和 `deleteUploadFile` 变更通过媒体文件 `id` 更新或删除已上传的文件（参见 [媒体文件的变更](#mutations-on-media-files)）。

🌐 The GraphQL API does not support media upload. Use the [REST API `POST /upload` endpoint](/cms/api/rest/upload) for all file uploads and use the returned info to link to it in content types. You can still update or delete uploaded files with the `updateUploadFile` and `deleteUploadFile` mutations using media files `id` (see [mutations on media files](#mutations-on-media-files)).

:::

:::caution `documentId` only

GraphQL API 仅使用 `documentId` 字段公开文档。之前的数字 `id` 在这里不再可用，尽管它仍然通过 REST API 返回以保持向后兼容性（详见 [breaking change](/cms/migration/v4-to-v5/breaking-changes/use-document-id)）。

🌐 The GraphQL API exposes documents using only the `documentId` field. The previous numeric `id` is no longer available here, although it is still returned by the REST API for backward compatibility (see [breaking change](/cms/migration/v4-to-v5/breaking-changes/use-document-id) for details).

:::

## 查询 {#queries}

🌐 Queries

GraphQL 中的查询用于获取数据而不修改数据。

🌐 Queries in GraphQL are used to fetch data without modifying it.

当将内容类型添加到你的项目时，2 个自动生成的 GraphQL 查询将添加到你的架构中，以内容类型的单数和复数 API ID 命名，如下例所示：

🌐 When a content-type is added to your project, 2 automatically generated GraphQL queries are added to your schema, named after the content-type's singular and plural API IDs, as in the following example:

| 内容类型显示名称 | 单数 API ID | 复数 API ID |
|----------------|-----------------|---------------|
| 餐厅 | `restaurant` | `restaurants` |

<details>
<summary>单数 API ID 与复数 API ID：</summary>

在内容类型生成器中创建内容类型时，会定义单数 API ID 和复数 API ID 值，并且可以在管理面板中编辑内容类型时找到（参见 [用户指南](/cms/features/content-type-builder#creating-content-types)）。你可以在创建内容类型时定义自定义 API ID，但之后无法修改这些 ID。

🌐 Singular API ID and Plural API ID values are defined when creating a content-type in the Content-Type Builder, and can be found while editing a content-type in the admin panel (see [User Guide](/cms/features/content-type-builder#creating-content-types)). You can define custom API IDs while creating the content-type, but these can not modified afterwards.

</details>

### 获取单个文档 {#fetch-a-single-document}

🌐 Fetch a single document

可以通过它们的 `documentId` 获取文档  。

```graphql title="Example query: Find a restaurant with its documentId"
{
  restaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl") {
    name
    description
  }
}
```

### 获取多个文档 {#fetch-multiple-documents}

🌐 Fetch multiple documents

要获取多个文档  你可以使用简单的、扁平的查询或 [Relay-style](https://www.apollographql.com/docs/technotes/TN0029-relay-style-connections/) 查询：

扁平查询只返回每个文档中请求的字段。Relay 风格查询以 `_connection` 结尾，并返回一个 `nodes` 数组以及一个 `pageInfo` 对象。当你需要分页元数据时，请使用 Relay 风格查询。

🌐 Flat queries return only the requested fields for each document. Relay-style queries end with `_connection` and return a `nodes` array together with a `pageInfo` object. Use Relay-style queries when you need pagination metadata.

要获取多个文档，你可以使用如下所示的扁平查询：

🌐 To fetch multiple documents you can use flat queries like the following:

```graphql title="Example query: Find all restaurants"
restaurants {
  documentId
  title
}
```

关系也可以通过文档服务 API 连接、断开和设置，就像 REST API 一样（有关示例，请参阅 XX1）。

🌐 Relay-style queries can be used to fetch multiple documents and return meta information:

```graphql title="Example query: Find all restaurants"
{
  restaurants_connection {
    nodes {
      documentId
      name
    }
    pageInfo {
      pageSize
      page
      pageCount
      total
    }
  }
}
```

#### 获取关系 {#fetch-relations}

🌐 Fetch relations

你可以在你的扁平查询或你的 [Relay-style](https://www.apollographql.com/docs/technotes/TN0029-relay-style-connections/) 查询中请求包含关联数据：

<Tabs groupId="flat-relay">

以下示例获取所有“Restaurant”内容类型的文档，并且对于每个文档，还返回与“Category”内容类型的多对多关系的一些字段：

🌐 The following example fetches all documents from the "Restaurant" content-type, and for each of them, also returns some fields for the many-to-many relation with the "Category" content-type:

```graphql title="Example query: Find all restaurants and their associated categories"
{
  restaurants {
    documentId
    name
    description
    # categories is a many-to-many relation
    categories {
      documentId
      name
    }
  }
}
```

<TabItem value="relay" label="Relay-style queries">

以下示例使用 Relay 风格的查询从 “Restaurant” 内容类型中获取所有文档，并且对于每个餐厅，还返回与 “Category” 内容类型的多对多关系的一些字段：

🌐 The following example fetches all documents from the "Restaurant" content-type using a Relay-style query, and for each restaurant, also returns some fields for the many-to-many relation with the "Category" content-type:

```graphql title="Example query: Find all restaurants and their associated categories"
{
  restaurants_connection {
    nodes {
      documentId
      name
      description
      # categories is a many-to-many relation
      categories_connection {
        nodes {
          documentId
          name
        } 
      }
    }
    pageInfo {
      page
      pageCount
      pageSize
      total
    }
  }
}
```

:::info

目前，`pageInfo` 仅适用于一级文档。Strapi 的未来版本可能会为关系实现 `pageInfo`。

🌐 For now, `pageInfo` only works for documents at the first level. Future implementations of Strapi might implement `pageInfo` for relations.

<details>
<summary><code>pageInfo</code> 的可能用例：</summary>

这个可行：

```graphql
{
  restaurants_connection {
    nodes {
      documentId
      name
      description
      # many-to-many relation
      categories_connection {
        nodes {
          documentId
          name
        } 
      }
    }
    pageInfo {
      page
      pageCount
      pageSize
      total
    }
  }
}
```
这不起作用：

```graphql {13-19}
{
  restaurants_connection {
    nodes {
      documentId
      name
      description
      # many-to-many relation
      categories_connection {
        nodes {
          documentId
          name
        }
        # not supported
        pageInfo {
          page
          pageCount
          pageSize
          total
        }
      }
    }
    pageInfo {
      page
      pageCount
      pageSize
      total
    }
  }
}}
```
</details>

:::

### 获取媒体字段 {#fetch-media-fields}

🌐 Fetch media fields

媒体字段内容的获取方式与其他属性一样。

🌐 Media fields content is fetched just like other attributes.

以下示例获取“Restaurants”内容类型中附加到每个文档的每个 `cover` 媒体字段的 `url` 属性值：

🌐 The following example fetches the `url` attribute value for each `cover` media field attached to each document from the "Restaurants" content-type:

```graphql
{
  restaurants {
    images {
      documentId
      url
    }
  }
}
```

对于多个媒体字段，你可以使用扁平查询或 [Relay-style](https://www.apollographql.com/docs/technotes/TN0029-relay-style-connections/) 查询：

以下示例从“餐厅”内容类型中找到的 `images` 多媒体字段获取一些属性：

🌐 The following example fetches some attributes from the `images` multiple media field found in the "Restaurant" content-type:

```graphql
{
  restaurants {
    images_connection {
      nodes {
        documentId
        url
      }
    }
  }
}
```

以下示例使用 Relay 风格的查询从“Restaurant”内容类型中的 `images` 多媒体字段获取一些属性：

🌐 The following example fetches some attributes from the `images` multiple media field found in the "Restaurant" content-type using a Relay-style query:

```graphql
{
  restaurants {
    images_connection {
      nodes {
        documentId
        url
      }
    }
  }
}
```

:::info

目前，`pageInfo` 仅适用于文档。未来版本的 Strapi 可能也会在媒体字段 `_connection` 上实现 `pageInfo`。

🌐 For now, `pageInfo` only works for documents. Future implementations of Strapi might implement `pageInfo` for the media fields `_connection` too.

:::

### 获取组件 {#fetch-components}

🌐 Fetch components

组件内容的获取方式与其他属性一样。

🌐 Components content is fetched just like other attributes.

以下示例获取每个文档中添加的每个 `closingPeriod` 组件的 `label`、`start_date` 和 `end_date` 属性值，这些文档来自“餐馆”内容类型：

🌐 The following example fetches the `label`, `start_date`, and `end_date` attributes values for each `closingPeriod` component added to each document from the "Restaurants" content-type:

```graphql
{
  restaurants {
    closingPeriod {
      label
      start_date
      end_date
    }
  }
}
```

### 获取动态区域数据 {#fetch-dynamic-zone-data}

🌐 Fetch dynamic zone data

动态区域是在 GraphQL 中的联合类型，因此你需要使用 [fragments](https://www.apollographql.com/docs/react/data/fragments/) （即使用 `...on`）来查询字段，并将组件名称（使用 `ComponentCategoryComponentname` 语法）传递给 [`__typename`](https://www.apollographql.com/docs/apollo-server/schema/schema/#the-__typename-field)：

以下示例获取可以添加到“dz”动态区域的“Default”组件类别中“Closingperiod”组件的 `label` 属性的数据：

🌐 The following example fetches data for the `label` attribute of a "Closingperiod" component from the "Default" components category that can be added to the "dz" dynamic zone:

```graphql
{
  restaurants {
    dz {
      __typename
      ...on ComponentDefaultClosingperiod {
        # define which attributes to return for the component
        label
      }
    }
  }
}
```

### 获取草稿或已发布的版本 {#status}

🌐 Fetch draft or published versions 

如果内容类型启用了 [Draft & Publish](/cms/features/draft-and-publish) 功能，你可以在查询中添加 `status` 参数以获取文档的草稿或已发布版本 ：

```graphql title="Example: Fetch draft versions of documents"
query Query($status: PublicationStatus) {
  restaurants(status: DRAFT) {
    documentId
    name
    publishedAt # should return null
  }
}
```

```graphql title="Example: Fetch published versions of documents"
query Query($status: PublicationStatus) {
  restaurants(status: PUBLISHED) {
    documentId
    name
    publishedAt
  }
}
```

### 使用 `publicationFilter` 过滤 {#publication-filter}

🌐 Filter with `publicationFilter` 

如果启用了 [Draft & Publish](/cms/features/draft-and-publish) 功能，你可以在内置集合和单类型查询中添加 `publicationFilter` 参数。它根据[草稿版本和已发布版本之间的关系](/cms/api/document-service/publication-filter)筛选文档：例如，从未发布的草稿，或者自上次发布以来被修改的条目。GraphQL 通过 `PublicationFilter` 枚举暴露与 REST API 和文档服务 API 相同的值。

🌐 If the [Draft & Publish](/cms/features/draft-and-publish) feature is enabled, you can add a `publicationFilter` argument to built-in collection and single-type queries. It filters documents by the [relationship between their draft and published versions](/cms/api/document-service/publication-filter): for example, drafts that were never published, or entries modified since they were last published. GraphQL exposes the same values as the REST API and the Document Service API through the `PublicationFilter` enum.

`publicationFilter` 首先选择文档组；然后 `status` 参数决定每个结果返回其草稿行还是已发布行。

:::caution

当省略 `status` 时，GraphQL 会在应用 `publicationFilter` 之前默认使用 `PUBLISHED`（与 REST 相同）。草稿类值如 `NEVER_PUBLISHED` 不会返回结果，除非你传入 `status: DRAFT`。

🌐 When `status` is omitted, GraphQL defaults to `PUBLISHED` before applying `publicationFilter` (same as REST). Draft-only values such as `NEVER_PUBLISHED` return no results unless you pass `status: DRAFT`.

:::

```graphql title="Example: Fetch never-published draft documents"
query Query($status: PublicationStatus, $publicationFilter: PublicationFilter) {
  restaurants(status: DRAFT, publicationFilter: NEVER_PUBLISHED) {
    documentId
    name
    publishedAt
  }
}
```

```graphql title="Example: Modified documents with default PUBLISHED status"
query Query {
  restaurants(publicationFilter: MODIFIED) {
    documentId
    name
    publishedAt
  }
}
```

可用的枚举值：

🌐 Available enum values:

| GraphQL 枚举 | 文档服务 / REST 值 |
| --- | --- |
| `NEVER_PUBLISHED` | `never-published` |
| `HAS_PUBLISHED_VERSION` | `has-published-version` |
| `MODIFIED` | `modified` |
| `UNMODIFIED` | `unmodified` |
| `NEVER_PUBLISHED_DOCUMENT` | `never-published-document` |
| `HAS_PUBLISHED_VERSION_DOCUMENT` | `has-published-version-document` |
| `PUBLISHED_WITHOUT_DRAFT` | `published-without-draft`（[仅诊断](/cms/api/document-service/publication-filter#diagnostics)） |
| `PUBLISHED_WITH_DRAFT` | `published-with-draft`（[仅诊断](/cms/api/document-service/publication-filter#diagnostics)） |

要了解更多信息，请参阅文档服务 API 页面上的[用例和接受的值](/cms/api/document-service/publication-filter#values)。

🌐 To learn more, see the [use cases and accepted values](/cms/api/document-service/publication-filter#values) on the Document Service API page.

## 突变 {#mutations}

🌐 Mutations

GraphQL 中的突变用于修改数据（例如创建、更新和删除数据）。

🌐 Mutations in GraphQL are used to modify data (e.g. create, update, and delete data).

当将内容类型添加到你的项目时，将向你的架构添加 3 个自动生成的 GraphQL 修改，用于创建、更新和删除文档 。

例如，对于“餐厅”内容类型，会生成以下变更：

🌐 For instance, for a "Restaurant" content-type, the following mutations are generated:

| 用例 | 单一 API ID |
|---------------------------------------------|---------------------|
| 创建一个新的“餐厅”文档 | `createRestaurant` |
| 更新一个现有的“餐厅”餐厅 | `updateRestaurant` |
| 删除一个现有的“餐厅”餐厅 | `deleteRestaurant` |

### 创建新文档 {#create-a-new-document}

🌐 Create a new document

在创建新文档时，`data` 参数将具有与你的内容类型特定相关的输入类型。

🌐 When creating new documents, the `data` argument will have an associated input type that is specific to your content-type.

例如，如果你的 Strapi 项目包含“餐厅”内容类型，你将拥有以下内容：

🌐 For instance, if your Strapi project contains the "Restaurant" content-type, you will have the following:

| 突变 | 参数 | 输入类型 |
|--------------------|------------------|--------------------|
| `createRestaurant` | `data` | `RestaurantInput!` |

以下示例为“餐厅”内容类型创建一个新文档，并返回其 `name` 和 `documentId`：

🌐 The following example creates a new document for the "Restaurant" content-type and returns its `name` and `documentId`:

```graphql
mutation CreateRestaurant($data: RestaurantInput!) {
  createRestaurant(data: {
    name: "Pizzeria Arrivederci"
  }) {
    name
    documentId
  }
}
```

创建新文档时，会自动生成一个 `documentId`。

🌐 When creating a new document, a `documentId` is automatically generated.

突变的实现也支持关系属性。例如，你可以创建一个新的“类别”，并通过编写如下查询，将许多“餐馆”（使用它们的 `documentId`）附加到它上面：

🌐 The implementation of the mutations also supports relational attributes. For example, you can create a new "Category" and attach many "Restaurants" (using their `documentId`) to it by writing your query like follows:

```graphql
mutation CreateCategory {
  createCategory(data: { 
    Name: "Italian Food"
    restaurants: ["a1b2c3d4e5d6f7g8h9i0jkl", "bf97tfdumkcc8ptahkng4puo"]
  }) {
    documentId
    Name
    restaurants {
      documentId
      name
    }
  }
}
```

:::tip

如果你的内容类型启用了国际化 (i18n) 功能，你可以为特定的区域创建文档（参见 [创建新的本地化文档](/cms/api/graphql#locale-create)）。

🌐 If the Internationalization (i18n) feature is enabled for your content-type, you can create a document for a specific locale (see [create a new localized document](/cms/api/graphql#locale-create)).

:::

### 更新现有文档 {#update-an-existing-document}

🌐 Update an existing document

在更新现有文档 时，传递包含新内容的 `documentId` 和 `data` 对象。`data` 参数将具有与你的内容类型特定的关联输入类型。

例如，如果你的 Strapi 项目包含“餐厅”内容类型，你将拥有以下内容：

🌐 For instance, if your Strapi project contains the "Restaurant" content-type, you will have the following:

| 突变 | 参数 | 输入类型 |
|--------------------|------------------|--------------------|
| `updateRestaurant` | `data` | `RestaurantInput!` |

例如，以下示例会更新一个现有的“餐厅”内容类型的文档，并给它一个新名称：

🌐 For instance, the following example updates an existing document from the "Restaurants" content-type and give it a new name:

```graphql
mutation UpdateRestaurant($documentId: ID!, $data: RestaurantInput!) {
  updateRestaurant(
    documentId: "bf97tfdumkcc8ptahkng4puo",
    data: { name: "Pizzeria Amore" }
  ) {
    documentId
    name
  }
}
```

:::tip

如果为你的内容类型启用了国际化 (i18n) 功能，你可以为特定的区域创建文档（参见 [i18n 文档](/cms/api/graphql#locale-update)）。

🌐 If the Internationalization (i18n) feature is enabled for your content-type, you can create a document for a specific locale (see [i18n documentation](/cms/api/graphql#locale-update)).

:::

#### 更新关系 {#update-relations}

🌐 Update relations

你可以通过传递一个 `documentId` 或一个 `documentId` 数组（取决于关系类型）来更新关系属性。

🌐 You can update relational attributes by passing a `documentId` or an array of `documentId` (depending on the relation type).

例如，以下示例会更新“Restaurant”内容类型的文档，并通过 `categories` 关联字段向“Category”内容类型的文档添加关联：

🌐 For instance, the following example updates a document from the "Restaurant" content-type and adds a relation to a document from the "Category" content-type through the `categories` relation field:

```graphql
mutation UpdateRestaurant($documentId: ID!, $data: RestaurantInput!) {
  updateRestaurant(
    documentId: "slwsiopkelrpxpvpc27953je",
    data: { categories: ["kbbvj00fjiqoaj85vmylwi17"] }
  ) {
    documentId
    name
    categories {
      documentId
      Name
    }
  }
}
```

### 删除文档 {#delete-a-document}

🌐 Delete a document

要删除文档 ，传入其 `documentId` ：

```graphql
mutation DeleteRestaurant {
  deleteRestaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl") {
    documentId
  }
}
```

:::tip

如果你的内容类型启用了国际化 (i18n) 功能，你可以删除文档的特定本地化版本（参见 [i18n 文档](/cms/api/graphql#locale-delete)）。

🌐 If the Internationalization (i18n) feature is enabled for your content-type, you can delete a specific localized version of a document (see [i18n documentation](/cms/api/graphql#locale-delete)).

:::

### 媒体文件的修改 {#mutations-on-media-files}

🌐 Mutations on media files

:::caution

目前，媒体字段上的变更使用 Strapi v4 `id`，而不是 Strapi 5 `documentId`，作为媒体文件的唯一标识符。

🌐 Currently, mutations on media fields use Strapi v4 `id`, not Strapi 5 `documentId`, as unique identifiers for media files.

:::

媒体字段的变更使用文件 `id`。然而，Strapi 5 中的 GraphQL API 查询不再返回 `id`。可以找到媒体文件 `id`：

🌐 Media fields mutations use files `id`. However, GraphQL API queries in Strapi 5 do not return `id` anymore. Media files `id` can be found:

- 也可以在管理员面板的[媒体库](/cms/features/media-library)中，

- 或者通过发送 REST API `GET` 请求来[填充媒体文件](/cms/api/rest/populate-select#population)，因为 REST API 请求目前会返回媒体文件的 `id` 和 `documentId`。

#### 更新已上传的媒体文件 {#update-an-uploaded-media-file}

🌐 Update an uploaded media file

在更新已上传的媒体文件时，传入媒体的 `id`（而不是它的 `documentId`）以及包含新内容的 `info` 对象。`info` 参数将具有与媒体文件特定相关的输入类型。

🌐 When updating an uploaded media file, pass the media's `id` (not its `documentId`) and the `info` object containing new content. The `info` argument will has an associated input type that is specific to media files.

例如，如果你的 Strapi 项目包含“餐厅”内容类型，你将拥有以下内容：

🌐 For instance, if your Strapi project contains the "Restaurant" content-type, you will have the following:

| 突变 | 参数 | 输入类型 |
|--------------------|------------------|--------------------|
| `updateUploadFile` | `info` | `FileInfoInput!` |

例如，下面的示例更新了 `id` 为 3 的媒体文件的 `alternativeText` 属性：

🌐 For instance, the following example updates the `alternativeText` attribute for a media file whose `id` is 3:

```graphql
mutation Mutation($updateUploadFileId: ID!, $info: FileInfoInput) {
  updateUploadFile(
    id: 3,
    info: {
      alternativeText: "New alt text"
    }
  ) {
    documentId
    url
    alternativeText
  }
}
```

:::tip

如果上传变更返回禁止访问错误，请确保为上传插件设置了适当的权限（参见[用户指南](/cms/features/users-permissions#editing-a-role)）。

🌐 If upload mutations return a forbidden access error, ensure proper permissions are set for the Upload plugin (see [User Guide](/cms/features/users-permissions#editing-a-role)).

:::

#### 删除已上传的媒体文件 {#delete-an-uploaded-media-file}

🌐 Delete an uploaded media file

在删除已上传的媒体文件时，传递媒体的 `id`（而不是它的 `documentId`）。

🌐 When deleting an uploaded media file, pass the media's `id` (not its `documentId`).

```graphql title="Example: Delete the media file with id 4"
mutation DeleteUploadFile($deleteUploadFileId: ID!) {
  deleteUploadFile(id: 4) {
    documentId # return its documentId
  }
}
```

:::tip

如果上传变更返回禁止访问错误，请确保为上传插件设置了适当的权限（参见[用户指南](/cms/features/users-permissions#editing-a-role)）。

🌐 If upload mutations return a forbidden access error, ensure proper permissions are set for the Upload plugin (see [User Guide](/cms/features/users-permissions#editing-a-role)).

:::

## 过滤器 {#filters}

🌐 Filters

查询可以接受带有以下语法的 `filters` 参数：

🌐 Queries can accept a `filters` parameter with the following syntax:

`filters: { field: { operator: value } }`

多个筛选器可以组合在一起，逻辑运算符（`and`、`or`、`not`）也可以使用，并且接受对象数组。当多个字段条件被组合时，它们会默认使用 `and` 连接。

🌐 Multiple filters can be combined together, and logical operators (`and`, `or`, `not`) can also be used and accept arrays of objects. When multiple field conditions are combined, they are implicitly joined with `and`.

:::tip

`and`、`or` 和 `not` 运算符可以互相嵌套。

:::

可以使用以下运算符：

🌐 The following operators are available:

| 操作符 | 描述 |
| --- | --- |
| `eq` | 等于 |
| `eqi` | 等于，忽略大小写 |
| `ne` | 不等于 |
| `nei` | 不等于，忽略大小写 |
| `lt` | 小于 |
| `lte` | 小于或等于 |
| `gt` | 大于 |
| `gte` | 大于或等于 |
| `in` | 包含于数组中 |
| `notIn` | 不包含于数组中 |
| `contains` | 包含，区分大小写 |
| `notContains` | 不包含，区分大小写 |
| `containsi` | 包含，忽略大小写 |
| `notContainsi` | 不包含，忽略大小写 |
| `null` | 为空 |
| `notNull` | 不为空 |
| `between` | 介于之间 |
| `startsWith` | 以...开头 |
| `endsWith` | 以...结尾 |
| `and` | 逻辑 `and` |
| `or` | 逻辑 `or` |
| `not` | 逻辑 `not` |

```graphql title="Simple examples for comparison operators (eq, ne, lt, lte, gt, gte, between)"
# eq - returns restaurants with the exact name "Biscotte"
{
  restaurants(filters: { name: { eq: "Biscotte" } }) {
    name
  }
}

# eqi - returns restaurants whose name equals "Biscotte",
#       comparison is case-insensitive
{
  restaurants(filters: { name: { eqi: "Biscotte" } }) {
    name
  }
}

# ne - returns restaurants whose name is not "Biscotte"
{
  restaurants(filters: { name: { ne: "Biscotte" } }) {
    name
  }
}

# nei - returns restaurants whose name is not "Biscotte",
#       comparison is case-insensitive
{
  restaurants(filters: { name: { nei: "Biscotte" } }) {
    name
  }
}

# lt - returns restaurants with averagePrice less than 20
{
  restaurants(filters: { averagePrice: { lt: 20 } }) {
    name
  }
}

# lte - returns restaurants with averagePrice less than or equal to 20
{
  restaurants(filters: { averagePrice: { lte: 20 } }) {
    name
  }
}

# gt - returns restaurants with averagePrice greater than 20
{
  restaurants(filters: { averagePrice: { gt: 20 } }) {
    name
  }
}

# gte - returns restaurants with averagePrice greater than or equal to 20
{
  restaurants(filters: { averagePrice: { gte: 20 } }) {
    name
  }
}

# between - returns restaurants with averagePrice between 10 and 30
{
  restaurants(filters: { averagePrice: { between: [10, 30] } }) {
    name
  }
}
```

```graphql title="Simple examples for membership operators (in, notIn)"
# in - returns restaurants with category either "pizza" or "burger"
{
  restaurants(filters: { category: { in: ["pizza", "burger"] } }) {
    name
  }
}

# notIn - returns restaurants whose category is neither "pizza" nor "burger"
{
  restaurants(filters: { category: { notIn: ["pizza", "burger"] } }) {
    name
  }
}
```

```graphql title="Simple examples for string matching operators (contains, notContains, containsi, notContains, startsWith, endsWith)"
# contains - returns restaurants whose name contains "Pizzeria"
{
  restaurants(filters: { name: { contains: "Pizzeria" } }) {
    name
  }
}

# notContains - returns restaurants whose name does NOT contain "Pizzeria"
{
  restaurants(filters: { name: { notContains: "Pizzeria" } }) {
    name
  }
}

# containsi - returns restaurants whose name contains "pizzeria" (case‑insensitive)
{
  restaurants(filters: { name: { containsi: "pizzeria" } }) {
    name
  }
}

# notContainsi - returns restaurants whose name does NOT contain "pizzeria" (case‑insensitive)
{
  restaurants(filters: { name: { notContainsi: "pizzeria" } }) {
    name
  }
}

# startsWith - returns restaurants whose name starts with "Pizza"
{
  restaurants(filters: { name: { startsWith: "Pizza" } }) {
    name
  }
}

# endsWith - returns restaurants whose name ends with "Inc"
{
  restaurants(filters: { name: { endsWith: "Inc" } }) {
    name
  }
}
```

```graphql title="Simple examples for null checks operators (null, notNull)"
# null - returns restaurants where description is null
{
  restaurants(filters: { description: { null: true } }) {
    name
  }
}

# notNull - returns restaurants where description is not null
{
  restaurants(filters: { description: { notNull: true } }) {
    name
  }
}
```

```graphql title="Simple examples for logical operators (and, or, not)"
# and - both category must be "pizza" AND averagePrice must be < 20
{
  restaurants(filters: {
    and: [
      { category: { eq: "pizza" } },
      { averagePrice: { lt: 20 } }
    ]
  }) {
    name
  }
}

# or - category is "pizza" OR category is "burger"
{
  restaurants(filters: {
    or: [
      { category: { eq: "pizza" } },
      { category: { eq: "burger" } }
    ]
  }) {
    name
  }
}

# not - category must NOT be "pizza"
{
  restaurants(filters: {
    not: { category: { eq: "pizza" } }
  }) {
    name
  }
}
```

```graphql title="Example with nested logical operators: use and, or, and not to find pizzerias under 20 euros"
{
  restaurants(
    filters: {
      and: [
        { not: { averagePrice: { gte: 20 } } }
        {
          or: [
            { name: { eq: "Pizzeria" } }
            { name: { startsWith: "Pizzeria" } }
          ]
        }
      ]
    }
  ) {
    documentId
    name
    averagePrice
  }
}
```

:::strapi Deep filtering with the various APIs

有关如何使用各种 API 进行深度过滤的示例，请参阅 [this blog article](https://strapi.io/blog/deep-filtering-alpha-26)。

:::

## 排序 {#sorting}

🌐 Sorting

查询可以接受带有以下语法的 `sort` 参数：

🌐 Queries can accept a `sort` parameter with the following syntax:

- 根据单个值排序：`sort: "value"`
- 根据多个值排序：`sort: ["value1", "value2"]`

排序顺序可以用 `:asc`（升序，默认，可省略）或 `:desc`（降序）来定义。

🌐 The sorting order can be defined with `:asc` (ascending order, default, can be omitted) or `:desc` (for descending order).

```graphql title="Example: Fetch and sort on name by ascending order"
{
  restaurants(sort: "name") {
    documentId
    name
  }
}
```

```graphql title="Example: Fetch and sort on average price by descending order"
{
  restaurants(sort: "averagePrice:desc") {
    documentId
    name
    averagePrice
  }
}
```

```graphql title="Example: Fetch and sort on title by ascending order, then on average price by descending order"
{
  restaurants(sort: ["name:asc", "averagePrice:desc"]) {
    documentId
    name
    averagePrice
  }
}
```

## 分页 {#pagination}

🌐 Pagination

[Relay-style](https://www.apollographql.com/docs/technotes/TN0029-relay-style-connections/) 查询可以接受一个`pagination`参数。结果可以通过页码或偏移量进行分页。

:::note

分页方法不能混用。始终要么使用 `page` 与 `pageSize`，要么使用 `start` 与 `limit`。

🌐 Pagination methods can not be mixed. Always use either `page` with `pageSize` or `start` with `limit`.

:::

### 按页分页 {#pagination-by-page}

🌐 Pagination by page

| 参数 | 描述 | 默认值 |
| --- | --- | --- |
| `pagination.page` | 页码 | 1 |
| `pagination.pageSize` | 每页条数 | 10 |

```graphql title="Example query: Pagination by page"
{
  restaurants_connection(pagination: { page: 1, pageSize: 10 }) {
    nodes {
      documentId
      name
    }
    pageInfo {
      page
      pageSize
      pageCount
      total
    }
  }
}
```

### 按偏移量分页 {#pagination-by-offset}

🌐 Pagination by offset

| 参数 | 描述 | 默认值 | 最大值 |
| --- | --- | --- | --- |
| `pagination.start` | 起始值 | 0 | - |
| `pagination.limit` | 返回的实体数量 | 10 | -1 |

```graphql title="Example query: Pagination by offset"
{
  restaurants_connection(pagination: { start: 10, limit: 19 }) {
    nodes {
      documentId
      name
    }
    pageInfo {
      page
      pageSize
      pageCount
      total
    }
  }
}
```

:::tip

`pagination.limit` 的默认值和最大值可以在 `./config/plugins.js` 文件中通过 `graphql.config.defaultLimit` 和 `graphql.config.maxLimit` 键进行配置。

🌐 The default and maximum values for `pagination.limit` can be [configured in the `./config/plugins.js`](/cms/plugins/graphql#code-based-configuration) file with the `graphql.config.defaultLimit` and `graphql.config.maxLimit` keys.

:::

:::note Many-to-many relation ordering with pagination

在使用分页查询多对多关系时，管理员面板中设置的自定义排序会被保留。如果在查询中对关系进行排序（例如，`categories(sort: "name")`），分页将遵循你指定的排序顺序，而不是内容管理器中配置的自定义排序。

🌐 When querying many-to-many relations with pagination, the custom order set in the admin panel is preserved. If you sort relations in a query (e.g., `categories(sort: "name")`), the pagination respects your specified sort order rather than the custom order configured in the content manager.

:::

## `locale` {#locale}

［国际化 (i18n)］(/cms/features/internationalization) 功能为 GraphQL API 添加了新功能：

🌐 The [Internationalization (i18n)](/cms/features/internationalization) feature adds new features to the GraphQL API:

- 在 GraphQL 模式中添加了 `locale` 字段。
- GraphQL 可以用于：
  - 使用 `locale` 参数查询特定区域的文档
  - 用于针对特定语言环境的文档进行[创建](#locale-create)、[更新](#locale-update)和[删除](#locale-delete)的变更

### 获取特定区域的所有文档 {#locale-fetch-all}

🌐 Fetch all documents in a specific locale 

要获取特定区域的所有文档  ，请将 `locale` 参数传递给查询：

```graphql
query {
  restaurants(locale: "fr") {
    documentId
    name
    locale
  }
}
```

```json
{
  "data": {
    "restaurants": [
      {
        "documentId": "a1b2c3d4e5d6f7g8h9i0jkl",
        "name": "Restaurant Biscotte",
        "locale": "fr"
      },
      {
        "documentId": "m9n8o7p6q5r4s3t2u1v0wxyz",
        "name": "Pizzeria Arrivederci",
        "locale": "fr"
      },
    ]
  }
}
```

### 获取特定语言环境的文档 {#locale-fetch}

🌐 Fetch a document in a specific locale 

要获取特定区域的文档  ，请将 `documentId` 和 `locale` 参数传递给查询：

**示例查询:**
```graphql
query Restaurant($documentId: ID!, $locale: I18NLocaleCode) {
  restaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl", locale: "fr") {
    documentId
    name
    description
    locale
  }
}
```

**示例响应:**
```json
{
  "data": {
    "restaurant": {
      "documentId": "lviw819d5htwvga8s3kovdij",
      "name": "Restaurant Biscotte",
      "description": "Bienvenue au restaurant Biscotte!",
      "locale": "fr"
    }
  }
}
```

### 创建一个新的本地化文档 {#locale-create}

🌐 Create a new localized document 

`locale` 字段可以传递以创建针对特定语言环境的本地化文档  （有关使用 GraphQL 进行变更的更多信息，请参阅 [GraphQL API 文档](/cms/api/graphql#create-a-new-document)）。

```graphql title="Example: Create a new restaurant for the French locale"
mutation CreateRestaurant($data: RestaurantInput!, $locale: I18NLocaleCode) {
  createRestaurant(
    data: {
      name: "Brasserie Bonjour",
      description: "Description in French goes here"
    },
    locale: "fr"
  ) {
  documentId
  name
  description
  locale
}
```

### 为特定地区更新文档 {#locale-update}

🌐 Update a document for a specific locale 

可以在变更中传入 `locale` 参数以更新给定语言环境的文档  （有关使用 GraphQL 的变更的更多信息，请参阅 [GraphQL API 文档](/cms/api/graphql#update-an-existing-document)）。

```graphql title="Example: Update the description field of restaurant for the French locale"
mutation UpdateRestaurant($documentId: ID!, $data: RestaurantInput!, $locale: I18NLocaleCode) {
  updateRestaurant(
    documentId: "a1b2c3d4e5d6f7g8h9i0jkl"
    data: {
      description: "New description in French"
    },
    locale: "fr"
  ) {
  documentId
  name
  description
  locale
}
```

### 删除文档的语言区域 {#locale-delete}

🌐 Delete a locale for a document 

在变更中传递 `locale` 参数以删除文档的特定本地化 ：

```graphql
mutation DeleteRestaurant($documentId: ID!, $locale: I18NLocaleCode) {
  deleteRestaurant(documentId: "xzmzdo4k0z73t9i68a7yx2kk", locale: "fr") {
    documentId
  }
}
```

## 高级用例 {#advanced-use-cases}

🌐 Advanced use cases

点击以下卡片，查看利用 GraphQL API 和 Strapi 功能的更高级用例的简短指南： 

🌐 Click on the following cards for short guides on more advanced use cases leveraging the GraphQL API and Strapi features: 

- [高级查询](/cms/api/graphql/advanced-queries): 查看 GraphQL API 的多级查询和自定义解析器链示例。
  - [高级政策](/cms/api/graphql/advanced-policies): 查看高级策略示例，例如 GraphQL API 的条件可见性和组成员资格。

:::info Aggregations not yet available

GraphQL 聚合（count、avg、sum、min、max、groupBy）尚未在 `@strapi/plugin-graphql` 中实现。当该功能可用时，本节将会更新。

🌐 GraphQL aggregations (count, avg, sum, min, max, groupBy) are not yet implemented in `@strapi/plugin-graphql`. This section will be updated when the feature becomes available.

与此同时，你可以通过 REST API 获取文档总数（例如，`GET /api/restaurants?pagination[pageSize]=1` 返回 `meta.pagination.total`），或者编写一个使用 [文档服务 API](/cms/api/document-service) 来计算聚合的 [自定义 GraphQL 解析器](/cms/api/graphql/advanced-queries#resolver-chains)。

🌐 In the meantime, you can get a total document count through the REST API (e.g., `GET /api/restaurants?pagination[pageSize]=1` returns `meta.pagination.total`), or write a [custom GraphQL resolver](/cms/api/graphql/advanced-queries#resolver-chains) that uses the [Document Service API](/cms/api/document-service) to compute aggregations.

:::
