Experience Edge Schema for Content Hub Tenants
Experience Edge hosts tenants for a number of different Sitecore systems including XM, XM Cloud, Content Hub and Content Hub ONE. However, the data models of the Content Hub based systems is different to that of the XM based systems, so each requires a different GraphQL schema to access the content which has been published to Experience Edge. In this post I’m going to explore the GraphQL schema which is generated for a Content Hub tenant.
The Content Hub data model is quite similar to the Sitecore XM data model in some respects, but quite different in many others. In XM the “documents” are called items, and their fields are defined by a template. In Content Hub the “documents” are called entities, and their properties are defined by an Entity Definition. The terminology used inside Experience Edge is the same used in Content Hub as Experience Edge started out initially as a service for Content Hub. The data model used in Experience Edge is flexible enough to store both Content Hub and XM structured data.
XM structures items into a hierarchical tree and items can reference other items through special fields. Content Hub (and Experience Edge) structure the entities into a graph. There is no hierarchical tree in which the entities are stored. Entities reference other entities through relations, which are different to properties.
Given the more general nature of the Content Hub data model, the GraphQL schema used in Experience Edge for Content Hub tenants must also be a bit more general than the one we have for XM, which I discussed previously in my post Experience Edge Schema for XM Tenants.
The GraphQL schema for a Content Hub tenant is dynamic. It is generated based on the Entity Definitions which have been published by the Content Hub instance. For each entity definition, types are added to the GraphQL schema to support retrieving a single entity or a list of entities, including the ability to filter using different criteria. Even the predicates used in the filtering criteria are generated based on the Entity Definition as the properties of the predicate need to match those of the Entities being filtered.
To execute any of the queries in this post, you can use the IDE which is accessible at https://edge.sitecorecloud.io/api/graphql/ide or you can use the tips I provided in my previous post about Using the Experience Edge GraphQL API. In either case, you’ll need a Content Hub tenant on Experience Edge and an API key for that tenant.
Top-level Query Fields
For each Entity Definition which is published to Experience Edge, the following fields are added to the top-level Query
type:
{name}
to find a single entity of that type, by it’s ID.all{name}
to retrieve a list of entities of that type. Optional filtering can be applied using thewhere
argument.
The {name}
in the fields is derived from the Entity Definition name but transformed to be a safe GraphQL field name. Any leading uppercase letter is switched to lowercase, and invalid field characters like dot (.
) are replaced with underscores (_
). For example, the query name used for M.Asset
is m_Asset
.
Entity Definition Types
To support the top-level query fields, a few more types are also added to the GraphQL schema:
{name}
which defines the fields available on the type.{name}List
which is a list of{name}
types.{name}Predicate
to allow filtering entities of that type.{name}Sorts
to allow specifying how sorting should be done for the type.

The fields available on the {name}
type depend on the properties and relations defined in the Entity Definition, and the publishing settings which have been set for those properties. By default, any new Entity Definition and any new properties and relations are enabled for publication to Experience Edge.
Let’s assume we have the following Entity Definition defined in Content Hub:
MyDefinition
Field Type | Field Name | Configuration |
---|---|---|
Property | Title | String - Single-Line |
Property | Content | String - HTML |
Relation | MyDefinitionToAsset | ManyToMany M.Asset |
The GraphQL schema includes a type for this Entity Definition:
1 | type MyDefinition { |
The fields of the type reflect the properties and relations defined on the Entity Definition. You might notice some extra fields on the type like createdBy
and modifiedOn
. These are standard fields which Content Hub includes when creating new Entity Definitions. The properties are scalar types like String
and DateTime
but the relations link through to the entities which are included in the relation. So when querying for an entity of type MyDefinition
, the linked assets in the MyDefinitionToAsset
relation can be accessed using the myDefinitionToAsset
field.
1 | query { |
The MyDefinitionToAsset
relation on the MyDefinition
Entity Definition has many-to-many cardinality, which will be exposed as a list of entities, not just a single entity. As you can see from the MyDefinition
type above, the type of the myDefinitionToAsset
field is M_AssetList!
. It’s a list of M_Asset
.
Find by ID
The query field named after the Entity Definition is used to locate an entity by it’s identifier. Although the argument is named id
, this is not to be confused with the Content Hub ID
which is numeric. The id
argument is required.
1 | query MyEntity { |
The response is of type MyDefinition
.
1 | { |
List by Type
The all
query field is used to list all entities of that type. Filtering can also be applied using the where
argument. Because the result will be a list, there are also arguments to support paging and sorting.
1 | query MyEntities { |
The response is of type MyDefinitionList
.
1 | { |
The above query will list the first page of entities which are based on the MyDefinition
Entity Definition, sorted by the id
field. The first
argument specifies the number of results on the page and defaults to 20. The where
argument can be used to filter the results returned based on criteria for each property and relation. The type of the where
argument is {name}Predicate
which includes fields for filtering on any of the fields defined by the Entity Definition. In this case, the where
argument is of type MyDefinitionPredicate
. The predicate type is also dynamically generated based on the Entity Definition and is defined in the GraphQL schema as:
1 | input MyDefinitionPredicate { |
As you can see, the predicate type includes fields to perform operations across all the fields of the MyDefinition
type.
The predicate type supports several comparison operations per property:
*_allOf
: Checks the field contains all of the provided values.*_anyOf
: Checks the field contains any of the provided values.*_contains
: Checks the field contains the provided value.*_doesnotcontain
: Checks the field does not contain the provided value.*_doesnotendwith
: Checks the field does not end with the provided value.*_doesnotstartwith
: Checks the field does not starts with the provided value.*_endswith
: Checks the field ends with the provided value.*_eq
: Checks the field is an exact match of the provided value.*_neq
: Checks the field is not an exact match of the provided value.*_noneOf
: Checks the field does not contain any of the provided values.*_startswith
: Checks the field starts with the provided value.
The predicate input type also includes fields to combine multiple predicates:
AND
: All predicates must matchOR
: Any predicate must match
The following query will filter the list of entities for those that have a title
property equal to “alpha”.
1 | query MyAlphaEntities { |
Now only the entity whose title is Alpha
is returned.
1 | { |
More complex predicates can be created by combining several predicates together. The following query will filter for MyDefinition
entities that contain the word “lorem” in the Content
field and that were created by the Administrator
user.
1 | query MyEntities { |
If the data set is larger, the results can be paged. Experience Edge supports cursor paging where each page of results returns an opaque cursor which can be passed back in the next query to get the next page of results. The cursor is available alongside other paging information in the pageInfo
field of the {name}List
type.
1 | query MyEntities { |
The response now includes information on the current page of results.
1 | { |
To get the next page of results, pass the pageInfo.endCursor
string returned in the above results into the after
argument of the all
query.
1 | query MyEntities { |
By default, the entities returned in a list are sorted by their ID. The orderBy
argument of the all
query can be specified to change the sort order. The orderBy
argument is of type [{name}Sorts]
. The sort type is generated based on the properties (not relations) of the Entity Definition.
1 | enum MyDefinitionSorts { |
Sorting can be specified either ascending or descending for each property.
1 | query MyEntities { |
The orderBy
argument accepts an array of {name}Sorts
, so multiple sorts can also be defined.
1 | query MyEntities { |
CMP Interfaces
The Content Hub Content Marketing Platform (CMP) functionality allows authors to easily define new content types. Under the hood, the content type UI is simply adding additional fields to the M_Content
entity definition. Having so many fields on a single type can make querying and discoverability a bit messy, so Experience Edge emits interfaces into the GraphQL schema for each CMP content type so CMP content can be treated as a specific type rather than just M_Content
all the time.
Content type definitions are treated the same way as entity definitions, with the following fields added to the Query
type in the GraphQL schema:
{ctname}
to find a single entity of that content type, by it’s ID.all_{ctname}
to retrieve a list of entities of that content type. Optional filtering can be applied using thewhere
argument.
The following types are also added to the GraphQL schema to support the querying capabilities.
{ctname}
which defines all fields available on the content type, including those fromM_Content
.I{ctname}
which defines only the fields of the content type.{ctname}List
which is a list of{ctname}
types.
In this case {ctname}
is the name of the content type, such as M_Content_Blog
.
The fields on the I{ctname}
interface type are only the fields defined on the content type. So for the OOTB Blog content type the interface is defined as such:
1 | interface IM_Content_Blog { |
In addition to the content type specific interfaces, the IM_Content
interface defines all the default properties for M_Content
. To provide access to these, the {ctname}
type implements both the I{ctname}
interface and the IM_Content
interface. When an entity based on M_Content
is going to be returned, the concrete {ctname}
is used instead. If querying over an array of M_Content
(such as the result of an allM_Content
query) inline fragments can be used to cast the IM_Content
to the proper type.
1 | query { |
The additional query fields also make querying for a specific content type easier. Without the query fields, to find all blogs, a where
argument must be used on the allM_Content
query:
1 | query { |
The allM_Content_Blog
query field removes the need for the where
argument.
1 | query { |
Conclusion
The GraphQL schema used by Content Hub tenants on Experience Edge is dynamically generated based on the Entity Definitions which have been enabled for publishing to Experience Edge in the Content Hub instance. Various types and added to the GraphQL schema for each Entity Definition to support the querying capabilities of the Delivery API.
Comments are closed
loading...