From 08eb6b7326eca9fd5229544cc6e6af48ba71573a Mon Sep 17 00:00:00 2001 From: meganelacheny Date: Wed, 19 Nov 2025 14:32:07 +0100 Subject: [PATCH] Aggregations section in GraphQL API documentation (#2810) * Add aggregations section in GraphQL API docs * Add section in GraphQL advanced queries * Fix mistakes * Fix inexistent anchor link/wrong page link --------- Co-authored-by: Pierre Wizla --- docusaurus/docs/cms/api/graphql.md | 113 ++++++++++++++++++ .../docs/cms/api/graphql/advanced-queries.md | 29 +++++ 2 files changed, 142 insertions(+) diff --git a/docusaurus/docs/cms/api/graphql.md b/docusaurus/docs/cms/api/graphql.md index 0e1853d81e..2b785e7364 100644 --- a/docusaurus/docs/cms/api/graphql.md +++ b/docusaurus/docs/cms/api/graphql.md @@ -425,6 +425,119 @@ query Query($status: PublicationStatus) { } ``` +## Aggregations + +Aggregations can be used to compute metrics such as counts, sums, or grouped totals without fetching every document individually. Aggregations are exposed through connection queries: every collection type includes an `aggregate` field under its `_connection` query. + +```graphql title="Example: Total restaurants matching a filter" +{ + restaurants_connection(filters: { categories: { documentId: { eq: "food-trucks" } } }) { + aggregate { + count + } + } +} +``` + +Aggregations follow the same filters, locale, publication status, and permissions as the parent query. For example, setting `locale: "fr"` or `status: DRAFT` on the connection limits the aggregation to those documents, and users can only aggregate content they are allowed to read. + +The table below lists all supported aggregation operators: + +| Operator | Description | Supported field types | +| --- | --- | --- | +| `count` | Returns the number of documents that match the query. | All content-types | +| `avg` | Computes the arithmetic mean per numeric field. | Number, integer, decimal | +| `sum` | Computes the total per numeric field. | Number, integer, decimal | +| `min` | Returns the smallest value per field. | Number, integer, decimal, date, datetime | +| `max` | Returns the largest value per field. | Number, integer, decimal, date, datetime | +| `groupBy` | Buckets results by unique values and exposes nested aggregations for each bucket. | Scalar fields (string, number, boolean, date, datetime), relations | + +:::note +Strapi ignores `null` values for `avg`, `sum`, `min`, and `max`. When aggregating relations, the operators run on the target documents and still respect their locales and permissions. +::: + +:::info Performance & limits +Aggregations operate server-side, so they are generally faster than downloading and processing large result sets on the client. However, complex `groupBy` trees and wide projections can still be expensive. Use filters to restrict the data set and consider setting up `depthLimit` and `amountLimit` values accordingly (see [available options](/cms/plugins/graphql#available-options)) to protect your API. Errors such as `You are not allowed to perform this action` usually mean the requester lacks the `Read` permission on the target collection. +::: + +### Aggregate multiple metrics in one request + +Aggregations can be combined so that one network round trip returns several metrics: + +```graphql title="Example: Average delivery time and minimum price" +{ + restaurants_connection(filters: { takeAway: { eq: true } }) { + aggregate { + avg { + delivery_time + } + min { + price_range + } + max { + price_range + } + } + } +} +``` + +### Group results + +Use `groupBy` to derive grouped metrics while optionally chaining further aggregations inside each group. Each group exposes the unique `key` and a nested connection that can be used for drilling down or counting the grouped items: + +```graphql title="Example: Count restaurants per category" +{ + restaurants_connection { + aggregate { + groupBy { + categories { + key + connection { + aggregate { + count + } + } + } + } + } + } +} +``` + +Groups inherit the top-level filters. To further refine a specific group, apply filters on the nested `connection`. + +### Combine with pagination and sorting + +Aggregations run on the entire result set that matches the query filters, not only on the current page. When a request includes pagination arguments and aggregations, the documents in `nodes` follow the pagination limits, but the values inside `aggregate` ignore `pageSize` or `limit` so they describe the whole set. You can still add sorting to order the documents returned with the aggregation results. + +```graphql title="Example: Paginate takeaway restaurants and count all matches" +{ + restaurants_connection( + filters: { takeAway: { eq: true } } + pagination: { page: 2, pageSize: 5 } + sort: "name:asc" + ) { + nodes { + documentId + name + rating + } + pageInfo { + page + pageSize + total + } + aggregate { + count + avg { + rating + } + } + } +} +``` + ## Mutations Mutations in GraphQL are used to modify data (e.g. create, update, and delete data). diff --git a/docusaurus/docs/cms/api/graphql/advanced-queries.md b/docusaurus/docs/cms/api/graphql/advanced-queries.md index 8db3a4d05f..87a2764039 100644 --- a/docusaurus/docs/cms/api/graphql/advanced-queries.md +++ b/docusaurus/docs/cms/api/graphql/advanced-queries.md @@ -50,3 +50,32 @@ export default { ``` In this example the parent resolver fetches restaurants using the [Document Service API](/cms/api/document-service), then delegates to the generated `Restaurant` resolver provided by the plugin so default behavior such as field selection still applies. + +## Aggregation drilldowns + + connections expose an `aggregate` field. You can use it to show high-level metrics and still let users drill into a specific group. Combine `groupBy` with pagination variables to keep responses small: + +```graphql +query RestaurantsByCity($first: Int, $after: String) { + restaurants_connection(pagination: { limit: $first, start: $after }) { + aggregate { + groupBy { + city { + key + connection(pagination: { limit: 5 }) { + aggregate { + count + } + nodes { + documentId + name + } + } + } + } + } + } +} +``` + +Use query variables to adjust the nested pagination when a user selects a group. Strapi runs the aggregation on the server, which is why the client only downloads the summary rows it needs: this keeps dashboards responsive even with large data sets. \ No newline at end of file