Skip to main content

GraphQL API

The GraphQL API allows performing queries and mutations to interact with the content-types through Strapi's GraphQL plugin. Results can be filtered, sorted and paginated.

Prerequisites

To use the GraphQL API, install the GraphQL plugin:

yarn add @strapi/plugin-graphql

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:

GraphQL playground use exampleGraphQL playground use example

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:

/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,
};
No GraphQL API to upload media files

The GraphQL API does not support media upload. Use the REST API POST /upload endpoint 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).

Caution
documentId only

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 for details).

Queries

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

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:

Content-type display nameSingular API IDPlural API ID
Restaurantrestaurantrestaurants
Singular API ID vs. Plural API 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). You can define custom API IDs while creating the content-type, but these can not modified afterwards.

Screenshot of the Content-Type Builder to retrieve singular and plural API IDsScreenshot of the Content-Type Builder to retrieve singular and plural API IDs

Fetch a single document

Documents can be fetched by their documentId.

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

Fetch multiple documents

To fetch multiple documents you can use simple, flat queries or Relay-style queries:

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:

Example query: Find all restaurants
restaurants {
documentId
title
}

Fetch relations

You can ask to include relation data in your flat queries or in your Relay-style queries:

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:

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

Fetch media fields

Media fields content is fetched just like other attributes.

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

{
restaurants {
images {
documentId
url
}
}
}

For multiple media fields, you can use flat queries or Relay-style queries:

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

{
restaurants {
images_connection {
nodes {
documentId
url
}
}
}
}

Fetch components

Components content is fetched just like other attributes.

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:

{
restaurants {
closingPeriod {
label
start_date
end_date
}
}
}

Fetch dynamic zone data

Dynamic zones are union types in GraphQL so you need to use fragments (i.e., with ...on) to query the fields, passing the component name (with the ComponentCategoryComponentname syntax) to `__typename`:

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:

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

Fetch draft or published versions

If the Draft & Publish feature is enabled for the content-type, you can add a status parameter to queries to fetch draft or published versions of documents :

Example: Fetch draft versions of documents
query Query($status: PublicationStatus) {
restaurants(status: DRAFT) {
documentId
name
publishedAt # should return null
}
}
Example: Fetch published versions of documents
query Query($status: PublicationStatus) {
restaurants(status: PUBLISHED) {
documentId
name
publishedAt
}
}

Aggregations

Aggregations can be used to compute metrics such as counts, sums, or grouped totals without fetching every document individually. Aggregations are exposed through Relay-style connection queries: every collection type includes an aggregate field under its <plural>_connection query.

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:

OperatorDescriptionSupported field types
countReturns the number of documents that match the query.All content-types
avgComputes the arithmetic mean per numeric field.Number, integer, decimal
sumComputes the total per numeric field.Number, integer, decimal
minReturns the smallest value per field.Number, integer, decimal, date, datetime
maxReturns the largest value per field.Number, integer, decimal, date, datetime
groupByBuckets 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.

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) 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:

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:

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.

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).

When a content-type is added to your project, 3 automatically generated GraphQL mutations to create, update, and delete documents are added to your schema.

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

Use caseSingular API ID
Create a new "Restaurant" documentcreateRestaurant
Update an existing "Restaurant" restaurantupdateRestaurant
Delete an existing "Restaurant" restaurantdeleteRestaurant

Create a new document

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

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

MutationArgumentInput type
createRestaurantdataRestaurantInput!

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

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

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

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:

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

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).

Update an existing document

When updating an existing document , pass the documentId and the data object containing new content. The data argument will have an associated input type that is specific to your content-type.

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

MutationArgumentInput type
updateRestaurantdataRestaurantInput!

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

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

If the Internationalization (i18n) feature is enabled for your content-type, you can create a document for a specific locale (see i18n documentation).

Update relations

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

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:

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

Delete a document

To delete a document , pass its documentId:

mutation DeleteRestaurant {
deleteRestaurant(documentId: "a1b2c3d4e5d6f7g8h9i0jkl") {
documentId
}
}
Tip

If the Internationalization (i18n) feature is enabled for your content-type, you can delete a specific localized version of a document (see i18n documentation).

Mutations on media files

Caution

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

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