Retrieve headless content

Every headless channel in Xperience by Kentico provides a GraphQL API endpoint, which allows you to retrieve content using HTTP requests and the JSON data format. Such headless content can be displayed or otherwise consumed by various types of applications, services or websites (e.g., mobile apps, external websites, single-page applications running within Xperience, etc.).

Before you can retrieve content using the headless API, you need to:

  1. Set up headless channels.
  2. Define content types for headless content.
  3. Add the required content by creating headless content items.

Once your headless channels are configured and the content is ready, you can:

  1. Explore the GraphQL schema and prepare queries for the desired content.
  2. Retrieve the content from your consuming applications or services by sending requests to the headless channel API endpoint.

Prepare GraphQL queries

Refer to the official GraphQL documentation to learn the general principles of queries.

Xperience generates a GraphQL schema for every headless channel, which includes:

  • Query fields and types for all headless content types assigned to the channel.
  • Types for all reusable content types that are linked through the fields of the content types included in the schema.
  • Interfaces for any reusable field schemas assigned to the included content types.
  • Scalar types for all data types used by the fields of the included content types.
  • Input types with the Filter and Sorter suffixes for all included content types and scalars.
  • Additional system types, e.g., representing fields containing linked media files, assets or pages.

The schema is automatically updated when you add/remove content types for headless channels or adjust their fields.

Only content retrieval is supported. The schema of headless channels does not include any GraphQL mutations for adding or modifying content.

To inspect the GraphQL schema of a headless channel, access the given channel’s API endpoint, e.g.:
https://example.com/graphql/09114e33-7578-4152-af81-62677bcca740

To learn how to find or configure a headless channel’s API endpoint, see Headless channel management.

There are several ways to explore the GraphQL schema and craft queries:

You need to have the AllowIntrospection option enabled for your application to explore the GraphQL schema.

  • Introspection – you can query the __schema field to retrieve information about the GraphQL schema and see available queries.

  • Banana Cake Pop – a developer IDE that allows you to explore the schema using a graphical user interface, as well as create and run queries to see the results immediately. You can download the desktop app or access the tool in a browser by adding /ui to your Xperience application’s GraphQL endpoint path. The tool’s default URL path is /graphql/ui, but can be customized via the GraphQlEndpointPath configuration key.

    Banana Cake Pop tool for crafting GraphQL queries

Basic querying

The GraphQL schema contains the following query fields and types for each headless content type assigned to the given channel:

Query field

Type

<contentTypeShortCodeName>

<ContentTypeShortCodeName>

<contentTypeShortCodeName>Collection

_<ContentTypeShortCodeName>Collection

Individual item types have fields matching the fields of the given content type in Xperience. Each Collection type contains an items sub-field, which allows you to access an array of the retrieved items.

The following query retrieves all headless items of the Acme.Product content type, loading the data in the name and price fields:



{
  acmeProductCollection {
    items {
      name
      price
    }
  }
}

Each item type also has the additional _system field(of the _System type) with general sub-fields like id and contentType



{
  acmeProductCollection {
    items {
      _system {
        id
        contentType
      }
      name
      price
    }
  }
}

To retrieve a specific item, use one of the following approaches:

  • Query a field storing a collection of items and filter the specific item using the where argument.

  • Query a single item field and set the id argument to the required content item’s GUID. This approach is suitable when you can populate the id argument using data retrieved by a previous query.

    
    
      {
        acmeProduct(id: "7aa56aa6-1d6f-4a3e-8f54-38e4ee3e4a62") {
          name
          price
        }
      }
    
      

Content restrictions for headless channels

Headless content does not include secured content items that have the Requires authentication flag enabled. 

Retrieval of latest or published headless content

Based on your needs, you may want to retrieve headless content in different workflow states. For example, to enable content editors to preview the headless content before they publish it, retrieve the content in its latest edited version. Alternatively, when retrieving headless content for your production environment, you typically want to retrieve only published versions of items.

To choose which version (latest or published) of headless content will be retrieved from Xperience, authenticate your requests with an API key using the corresponding access type.

When retrieving the latest version of headless items, linked items are also retrieved in their latest edited version.

Similarly, when retrieving published items, only published linked items are retrieved.

Languages

When querying item or collection fields, you can control which language variant is returned by setting the following arguments:

  • languageName – the code name of the language in which you want to retrieve the content item.
  • useLanguageFallbacks – indicates whether the closest language variant available in the fallback chain is retrieved if the item is not available in the requested language.


{
  acmeProductCollection(languageName: "es", useLanguageFallbacks: true) {
    items {
      name
      price
    }
  }
}

Linked items

Headless items may contain fields that link to other reusable content items or headless items. All linked content types are represented in the GraphQL schema according to the same rules described above, with both single item and collection types. This includes content types that are linked recursively through the fields of other linked types.

The following query retrieves headless items of the Acme.Product content type, with additional linked content items stored in the reviews field:



{
  acmeProductCollection {
    items {
      name
      price
      reviews {
        items {
          reviewText
          reviewStars
        }
      }
    }
  }
}

Linked pages

Content types may include fields that link to website channel pages (fields with the Pages data type in Xperience). Such fields are represented as a [_WebPage!] array in the GraphQL schema.

The _WebPage type exposes the url field, which stores the absolute URL of the given page containing the main domain of the related website channel. You cannot use the headless API to access the fields of the page content type itself. If you wish to associate other data with the linked page, create a reusable content type representing page links with a Pages field for selecting the target page, and additional fields for any required data (e.g., a name or title for the link).

In the following query example, a linked Promotion content type has a promotionArticlePages field linking to a website channel page.



{
  acmeProductCollection {
    items {
      name
      price
      promotions {
        items {
          promotionArticleTitle,
          promotionArticlePages {
            url
          }
        }
      }
    }
  }
}

Pages linked in rich text fields

If a page is linked within the content of a rich text field in Xperience and this field is queried by the headless API, the returned data contains a link with an absolute URL containing the domain under which the headless channel endpoint was accessed.

Fields referencing reusable field schemas

For content type fields that link to content items of multiple content types via reusable field schemas (using the content item selector), you can only directly access the properties of the reusable field schema (represented as an interface in the GraphQL schema). To access the properties of the underlying concrete content types, use inline fragments.



{
  acmeProductCollection {
    items {
      name
      price
      promotions {
        items {
          productTitle,
          productDescription

          ... on acmeGrinder {
              grinderText
          }
        }
      }
    }
  }
}

Assets

Content types in Xperience may have fields that store or link to file assets (images, videos, documents, etc.).

  • Content item asset fields of reusable content types storing content item assets are represented by the _ContentItemAsset type in the GraphQL schema.
  • Media files fields of any content type linking to one or more media library files are represented as an [_Asset!] array in the GraphQL schema.

The _ContentItemAsset and _Asset types expose various fields with the file’s metadata. The path field stores the relative URL path, which you can use to link or display the file.

In the following query example, a linked Promotion content type has a promotionTeaserImage asset field.



{
  acmeProductCollection {
    items {
      name
      price
      promotions {
        items {
          promotionArticleTitle 
          promotionTeaserImage {
            path
          }
        }
      }
    }
  }
}

Assets linked in rich text fields

If an image or other file is linked within the content of a rich text field in Xperience and this field is queried by the headless API, the returned data contains an absolute file URL containing the domain under which the headless channel endpoint was accessed.

Content item assets in latest versions

Retrieved URLs of content item assets in their latest version, both relative and absolute, are valid only for 10 minutes. If you want to access a content item asset after this limit, you need to retrieve the asset again.

Taxonomies

Content types in Xperience may have fields that store tags related to the content in taxonomy fields.

Taxonomy fields of content types storing tags are represented by the _Tag type in the GraphQL schema. The _Tag type exposes the following properties for each tag:

  • name – the code name of the tag.
  • title – the display name of the tag.
  • description – a brief description of the tag.

In the following query example, a Product content type has a productCategory taxonomy field.



{
  acmeProductCollection {
    items {
      name
      price
      productCategory {
        name
        title
        description
      }
    }
  }
}

Time fields

The values of Date and time fields (represented by the DateTime scalar type) are always retrieved in the time zone of the server where the application is running. If required, you can convert to a different time zone in the code of your client application.

Filtering

The GraphQL schema automatically includes input types with the Filter suffix for all included content types. These filters allow you to limit which items are returned when you query a *Collection field. Access the filters by adding the where argument to any collection field in your query.

The collection filters have fields matching the fields of the corresponding content type. These filter fields use scalar filter types with sub-fields representing comparison operations suitable for the given data type. For example: contains, eq (equals), nstartsWith (does not start with) for_TextFilter.

Each where argument can only filter over a single scalar field (combined AND/OR filters are not supported).

You can combine filtering with sorting arguments.

Acme.Product items whose name contains hammer


{
  acmeProductCollection(where: { name: { contains: "hammer"}}) {
    items {
      name
      price
    }    
  }
}

Acme.Product items whose price is greater than 50


{
  acmeProductCollection(where: { price: { gt: 50}}) {
    items {
      name
      price
    }
  }
}

All Acme.Product items, with linked review items that have 3 or more stars


{
  acmeProductCollection {
    items {
      name
      price
      reviews(where: { reviewStars: {gte: 3}}) {
        items {
          reviewText
          reviewStars
        }
      }
    }
  }
}

You can also filter collections based on taxonomy fields using the following operations:

  • containsAny – retrieves items that contain any of the specified tags or contain child tags of any of the specified tags.
  • containsAll – retrieves items that contain all of the specified tags or contain child tags of all of the specified tags.

In the following examples, productCategory is a taxonomy field (of type _Tag).

All Acme.Product items that are tagged as ‘Electronics’ or ‘Tool’


{
  acmeProductCollection (where: {productCategory: {containsAny: [ "Electronics", "Tool" ]}}) {
    items {
      name
      price
      productCategory {
        name
        title
        description
      }
    }
  }
}

Sorting

The GraphQL schema automatically includes input types with the Sorter suffix for all included content types. These sorters allow you to control the order in which items are returned when you query a *Collection field. Access the sorters by adding the sort argument to any collection field in your query.

The collection sorters have fields matching the fields of the corresponding content type. You can set the sorter fields to either the ASC (ascending) or DESC (descending) values from the _Sorter enum type.

You can combine sorting with filtering arguments.

Acme.Product items sorted by price in descending order


{
  acmeProductCollection(sort: { price: DESC }) {
    items {
      name
      price
    }
  }
}

Pagination

Pagination allows you to retrieve content items in smaller, more manageable segments. To control pagination, add the following arguments when you query a *Collection field:

  • skip – the index (0-based) of the item from which the retrieved segment starts. For example, if skip is 5, the segment starts from the 6th item.
  • take – how many items are retrieved in the segment, starting from the index set by skip (or the first item if skip is not specified). The maximum take value is 100.

Every collection type has the totalCount field, which returns the total number of retrieved items regardless of pagination applied by the skip and/or take arguments.

You can combine pagination with sorting and filtering arguments.

Retrieves the first page of 10 Acme.Product items


{
  acmeProductCollection(skip: 0, take: 10) {
    items {
      name
      price
    }
    totalCount
  }
}

Retrieve headless channel content

To retrieve headless channel content from your applications or services, send requests to the API endpoint of the appropriate headless channel, for example:
https://example.com/graphql/09114e33-7578-4152-af81-62677bcca740

To learn how to find or configure a headless channel’s API endpoint, see Headless channel management.

A standard GraphQL POST request has the Content-Type header set to application/json, and includes a JSON-encoded body containing a GraphQL query.

JSON request format


{
  'query': ...
}

Authentication

GraphQL API endpoints require all requests to have the Authorization header containing a valid and enabled API key for the given headless channel (see Headless channel management for more information about API keys).

HTTP Authorization header


Authorization: Bearer <ApiKey>

Authorizaton header security recommendations

If you set the Authorization header directly when sending requests from a client API on a public web page, it can be seen by anyone. To ensure the security of your API keys, we recommend that you use a backend proxy server to add the Authorization header and forward the requests to the GraphQL API endpoint.

Cross-origin resource sharing

For requests originating from the client-side code of web applications, you need to ensure Cross-Origin Resource Sharing (CORS).

To configure allowed origins:

  1. Open the Settings application in the Xperience administration.

  2. Navigate to the Content → Headless category.

  3. Fill in Allowed origins. You can enter the following values:

    • Domain names from which content retrieval requests will be sent, separated by semicolons. For example: www.example.com;sampleapp.net
    • The * wildcard, which means that headless API endpoints can be accessed by any origin. Some system configurations may not accept responses that allow all origins.
  4. Select Save.

Headless API endpoints now accept requests from the specified domains.

If you wish to configure different allowed origins for specific application environments (local development, QA, production, etc.), you can use the CMSHeadless:CorsAllowedOrigins key in your application’s configuration file (appsettings.json by default) or the HeadlessOptions.CorsAllowedOrigins option in your project’s startup file (Program.cs). See Headless channel management to learn more about configuring the headless API. However, keep in mind that the Allowed origins setting in Xperience overrides CORS allowed origin values set in your application’s configuration file or startup options.

Additionally, the CorsAllowedHeaders key or option allows you to restrict which HTTP headers are allowed for headless content retrieval requests (in addition to CORS-safelisted request headers). Separate the HTTP header names using semicolons. If not set, all headers are allowed by default.

appsettings.json example


{
  ...

  "CMSHeadless": {
    "CorsAllowedOrigins" : "www.example.com;sampleapp.net",
    "CorsAllowedHeaders": "Authorization;Cache-Control"
    ...
  }
}

When you send a request from a web application to a different domain, the user agent automatically includes the Origin HTTP header. If the domain name of the origin is allowed by your configuration, Xperience returns a response with the Access-Control-Allow-Origin header (set to either the origin domain or the * wildcard). The request is successful and the consuming application receives the requested data in the response’s payload.

If you do not set any allowed origins, headless API endpoints do not include the Access-Control-Allow-Origin header in responses, which may lead to failed requests. You can use an empty value if you wish to handle HTTP headers manually.

When sending responses to Preflight requests, the system includes the Access-Control-Allow-Headers header with a value containing the header names specified in the CorsAllowedHeaders option, or all header names sent in Access-Control-Request-Headers if the option is not set.