GraphQL Content API

The GraphQL Content API provides a GraphQL API interface to the content from Contentful. Each Contentful space comes with a GraphQL schema based on its content model. This GraphQL schema is generated at request time and is always up-to-date with the current status of the space.

You can use this API to consume both published and non-published content. Read more about this in the previewing content section.

Note: For EU data residency customers, the Base URL is https://graphql.eu.contentful.com.

Basic API Information

API Base URL https://graphql.contentful.com This is a read-only API

Basic API Information

The Contentful GraphQL Content API is available at:

https://graphql.contentful.com/content/v1/spaces/{SPACE}

It is also available for specific environments at:

https://graphql.contentful.com/content/v1/spaces/{SPACE}/environments/{ENVIRONMENT}

Disclaimer: GraphQL Content API is available on all spaces for customers on current pricing plans. If you are on a legacy plan, contact Customer Support to upgrade.

HTTP Methods

The GraphQL Content API supports both GET and POST methods.

This is the query used in both examples below:

1query($preview: Boolean){
2 blogCollection(preview: $preview){
3 items{
4 title
5 }
6 }
7}

POST

The HTTPS POST method is more flexible and recommended. The query should be sent as a property in a JSON payload in the body of the POST request with the property name query. Any required variables are added as an additional JSON property to the payload with the property name variables.

$curl -g \
>-X POST \
>-H "Content-Type: application/json" \
>-H "Authorization: Bearer {TOKEN}" \
>-d '{"query":"query($preview:Boolean){blogCollection(preview:$preview){items{title}}}","variables":{"preview":true}}' \
>https://graphql.contentful.com/content/v1/spaces/{SPACE}/environments/{ENVIRONMENT}

Supported values for the Content-Type header:

  • application/json
  • application/json; charset=UTF-8
  • application/x-www-form-urlencoded
  • application/x-www-form-urlencoded; charset=UTF-8

Using application/json is encouraged.

GET

The HTTPS GET method requires that the query is included in the URL string as a parameter. You can also send any required variables in an additional variables parameter in JSON format.

$curl -g \
>-X GET \
>-H "Content-Type: application/json" \
>-H "Authorization: Bearer {TOKEN}" \
>'https://graphql.contentful.com/content/v1/spaces/{SPACE}/environments/{ENVIRONMENT}?query=query($preview:Boolean){blogCollection(preview:$preview){items{title}}}&variables={"preview":true}'

Authentication

Any client using the API needs to provide an access token in either:

  • The Authorization header, specifically Authorization: Bearer MY_TOKEN.
  • The access_token URL query parameter.

The token must have access to the space and environment you’re targeting. For example, if you create an access token that only has access to the master environment of your space, you cannot use that token to access content from any other environment or space.

To learn more about authentication in Contentful and how to create your own access tokens, see the Authentication reference documentation.

API rate limits

API rate limits specify the number of requests a client can make to Contentful APIs in a specific time frame. Every request counts against a per-second rate limit.

There are no limits enforced on requests that hit our CDN cache, i.e. the request doesn’t count towards your rate limit and you can make an unlimited amount of cache hits. For requests that do hit the GraphQL Content API, a rate limit of 55 requests per second is enforced. Higher rate limits may apply depending on your current plan.

When a client gets rate limited, the API responds with the 429 Too Many Requests HTTP status code and sets the X-Contentful-RateLimit-Reset header that tells the client when it can make its next single request. The value of this header is an integer specifying the time before the limit resets and another request will be accepted. As the client is rate-limited per second, the header will return 1, which means the next second.

Example

The current rate limit for a client is the default 55 per second. Client: 85 uncached requests in 1 second

HTTP/1.1 429
X-Contentful-RateLimit-Reset: 1

Meaning: wait 1 second before making more requests.

The following table lists all headers returned in every response by the GraphQL API which give a client information on rate limiting:

HeaderDescription
X-Contentful-RateLimit-Second-LimitThe maximum amount of requests which can be made in a second.
X-Contentful-RateLimit-ResetThe number of seconds until the next request can be made.

Query complexity limits

Disclaimer: The default complexity limit for collections is 100. The query complexity is calculated as the maximum number of entries and assets a query can potentially return.

Query complexity limits specify the amount of data a client can request in one request. You can currently request up to 11000 entities in one request.

Example 1

1query {
2 lessonCollection(limit: 20) {
3 items {
4 title
5 }
6 }
7}

The query above can return up to 20 Lessons. The query complexity is 20.

Example 2

1query {
2 lessonCollection(limit: 20) {
3 items {
4 title
5 imageCollection(limit: 10) {
6 title
7 url
8 }
9 }
10 }
11}

The query above can return up to 20 Lessons and up to 200 Assets (up to 10 for each of 20 Lessons). The query complexity is 220.

Example 3

1query {
2 lessonCollection(limit: 20) {
3 items {
4 title
5 participantsCollection (limit: 10) {
6 ... on Person {
7 name
8 imageCollection(limit: 3) {
9 title
10 url
11 }
12 }
13 ... on Pet {
14 name
15 imageCollection(limit: 5) {
16 title
17 url
18 }
19 }
20 }
21 }
22 }
23}

The query above can return up to 20 Lessons, up to 200 Participants (up to 10 for each of 20 Lessons) and up to 1000 Assets (up to 5 for each of 200 Participants). The query complexity is 1220.

Parameters such as locale, useFallbackLocale, preview, sorting, filtering or image transformations do not change query complexity.

The API sets the X-Contentful-Graphql-Query-Cost response header to the calculated query complexity value.

When a client gets query complexity limited, the API responds with a TOO_COMPLEX_QUERY error.

Query size limits

Query size limits specify the maximum size of the query parameter for GET requests and the total payload size for POST requests. This limit is 8kb.

This limit includes whitespace and newline characters. Removing semantically unnecessary whitespaces and newline characters before sending a request can lower the query size. You can reduce the query size without manual editing using GraphQL minifiers, such as GQLMin.

When the query size of a request exceeds the limit, the API returns a QUERY_TOO_BIG error.

Note: You can use automatic persisted queries to bypass this limit if you are a customer on our Premium plan and above.

Rich Text

Rich Text fields work similarly when calculating complexity but have some special behaviour. The complexity of the links property in a RichText field is equal to the sum of the maximum number of allowed linked entries and assets in the validation settings for the field.

In the following example the richText field is configured with a maximum limit of 5 embedded inline entries and a maximum of 0 of all other types of embedded entries or assets:

1query{
2 articleCollection(limit: 100) {
3 items{
4 title
5 bodyRichText {
6 json
7 links {
8 entries {
9 inline {
10 sys {
11 id
12 }
13 }
14 }
15 }
16 }
17 }
18 }
19}

The query above can return up to 100 article entries and up to 500 links (up to 5 for each of 100 article) due to the field validation settings. The query complexity is 500.

By default a Rich Text field has a total limit of 1000 linked entities of all supported types. This means that by default the links field in each Rich Text entry has a complexity of 1000.

Tags

The ContentfulMetadata tags field calculates its complexity in a special way. The complexity of the tags property in the ContentfulMetadata field is 1 for every entry or asset being queried for. This complexity cost remains the same regardless of the number of tags returned.

1query {
2 articleCollection(limit: 100) {
3 items{
4 title
5 contentfulMetadata {
6 tags {
7 id
8 name
9 }
10 }
11 }
12 }
13}

The query above can return up to 100 Articles and up to 100 tags (up to 1 for each of 100 Articles). The query complexity is 200.

Previewing content

Accessing non-published content can be useful when you want to preview how a new article will look before publishing it. The GraphQL API gives you the control to choose whether you want to access published or non-published content in a very granular fashion.

To control whether you get published or non-published content you have to use the preview argument, available to both single resource fields and collection fields. This argument cascades, meaning that all the references resolved from a resource with preview: true are also showing preview content, unless explicitly overridden:

1query {
2 houseCollection (preview: true) {
3 items {
4 houseNumber
5 numberOfRooms
6 owner {
7 # content for "owner" will also be non-published
8 }
9 architect (preview: false) {
10 # content for "architect" will be published
11 }
12 }
13 }
14}

Any query that accesses non-published content requires a preview access token. This includes queries that mix preview and published content. Follow the authentication section to learn how to generate a token and how to use it to authenticate the requests. Fields in queries that require access to non-published content but fail to provide a valid preview access token will be resolved with an ACCESS_TOKEN_INVALID error.

Cursor pagination

Cursor pagination is an approach to paginating datasets in Contentful APIs. Unlike traditional offset-based pagination, which uses skip and limit parameters, cursor-based pagination uses opaque cursor tokens and dedicated next/prev links to mark the position in the dataset. This can dramatically improve performance, especially for datasets with large numbers of items.

How cursor pagination works

  • Initial request:

    Collections of entries and assets which support cursor based pagination are exposed as cursor collection types. They support the same arguments and types as collection types, with the exception of skip and the addition of pageNext, pagePrev and pages.

    1type FriendlyUserCursorCollection {
    2 # ... additional types and args
    3 pageNext: String
    4 pagePrev: String
    5 pages: CursorPages!
    6 items: [FriendlyUser]!
    7}
    8
    9type AssetCursorCollection {
    10 # ... additional types and args
    11 pageNext: String
    12 pagePrev: String
    13 pages: CursorPages!
    14 items: [Asset]!
    15}
    16
    17type CursorPages {
    18 next: String
    19 prev: String
    20}
    21
    22query {
    23 friendlyUserCursorCollection {
    24 items {
    25 name
    26 }
    27 pages {
    28 next
    29 prev
    30 }
    31 }
    32 assetCursorCollection {
    33 items {
    34 title
    35 }
    36 pages {
    37 next
    38 prev
    39 }
    40 }
    41}

    The response contains:

    • items: The current page of resources.
    • pages: Contains next (and optionally prev) URLs for paginating forward and backward.
  • Paginate forward: Use the pages.next URL from the previous response for the next page request:

    1query {
    2 friendlyUserCursorCollection(pageNext: {cursor_token}) {
    3 items {
    4 name
    5 }
    6 pages {
    7 next
    8 prev
    9 }
    10 }
    11 assetCursorCollection(pageNext: {cursor_token}) {
    12 items {
    13 title
    14 }
    15 pages {
    16 next
    17 prev
    18 }
    19 }
    20}

    Continue this process until the next link is omitted, which means you’ve reached the end of the dataset.

  • Paginate backward: If the response contains a pages.prev link, you can fetch previous pages:

    1query {
    2 friendlyUserCursorCollection(pagePrev: {cursor_token}) {
    3 items {
    4 name
    5 }
    6 pages {
    7 next
    8 prev
    9 }
    10 }
    11 assetCursorCollection(pagePrev: {cursor_token}) {
    12 items {
    13 title
    14 }
    15 pages {
    16 next
    17 prev
    18 }
    19 }
    20}
  • Consistency: All query parameters used in the initial request apart from limit are locked and encoded in the cursor token. They cannot be changed between pages.

    The limit parameter can be updated, and the new value will be persisted for subsequent pages until it’s updated again.

    1query {
    2 friendlyUserCursorCollection(pageNext: {cursor_token}, limit: {limit}) {
    3 items {
    4 name
    5 }
    6 pages {
    7 next
    8 prev
    9 }
    10 }
    11 assetCursorCollection(pageNext: {cursor_token}, limit: {limit}) {
    12 items {
    13 title
    14 }
    15 pages {
    16 next
    17 prev
    18 }
    19 }
    20}

    This can be useful when you need to adjust the page size, for example, if a page size (in bytes) exceeds the response limit.

  • Total count: The response does not include a total count or a skip property. This is a key feature for performance, as the API avoids costly full counts on large datasets.

Example response

1{
2 "data": {
3 "friendlyUserCursorCollection": {
4 "items": ["..."],
5 "pages": {
6 "next": "0IWECT7w....",
7 "prev": null
8 }
9 },
10 "assetCursorCollection": {
11 "items": ["..."],
12 "pages": {
13 "next": "3sibGlta....",
14 "prev": null
15 }
16 }
17 }
18}

Advantages

  • Considerable performance improvements, particularly for large datasets.

If your environment contains tens of thousands of entries or assets, consider switching to cursor pagination — it can make requests to fetch lists of entries or assets much faster.

If you don’t paginate at all and don’t need the total property from the response, we highly recommend using cursor collection types.

Limitations

  • The total property is not included in API responses. Make sure you don’t rely on it in your workflow before making the switch.

Enablement and access

  • The feature is currently available in the CMA, CPA, CDA and Graph API.