Experience Edge Schema for XM Tenants
Sitecore Experience Edge is a multi-tenant system hosting tenants for a number of different Sitecore source systems. The data models of each source system are quite different which requires a different schema in the GraphQL API for each kind of source system. In this post I'll be exploring the GraphQL schema used for XM tenants on Experience Edge. This schema is used by XM, XM cloud and managed cloud XM instances.
The Sitecore XM data model is a hierarchical tree of items. The fields of an item are defined in the data template used by the item. Although most (if not all) templates inherit from the standard template (to provide all the default functionality), developers define all the fields used by business users in their own data templates, which are then used in their implementations.
The GraphQL schema used for XM tenants on Experience Edge continues to evolve in a non-breaking manner, to add more functionality and expose addition data as required by features like headless SXA. Many of these new features require updating the XM Edge connector to the latest version, which is released as part of Sitecore Headless Rendering. For the samples in this post I'm using the Experience Edge tenant used by the MVP site, the SUGCON Europe and SUGCON ANZ sites. Big thank you to the Sitecore technical marketing team (especially Rob Earlam) for letting me use it for this post. These sites are hosted on XM cloud which is currently using the latest version of the XM Edge connector version 21.0. If you're using an older version, you might not see some of the fields mentioned below.
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 last post about Using the Experience Edge GraphQL API. In either case, you'll need an XM tenant on Experience Edge and an API key for that tenant.
Top-level Query Fields
There are 4 fields on the top-level Query
type in the XM Edge schema:
item
which allows accessing items by path or ID.layout
which allows resolving routes to items.search
which allows searching for items matching certain criteria.site
which allows accessing the sites that have been defined. Only available if using XM Edge connector v21 or later.
The Item Query
The item
query allows you to access a single item by either path or ID. You can query any of the fields or properties (such as name, display name, etc) on the item and also navigate the content tree through the children
and parent
fields.
Let's start with the "Hello World" of Sitecore queries and get the OOTB Home item.
query HomeItem {
item(language: "en", path: "/sitecore/content/home") {
id
name
}
}
This query will grab the home item and return it's ID and name.
{
"data": {
"item": {
"id": "110D559FDEA542EA9C1C8A5DF7E70EF9",
"name": "Home"
}
}
}
The item
query returns an Item
type in the GraphQL response. You can use the template
field of the Item
type to link to the template definition used by the item, and query it as well.
query HomeItem {
item(language: "en", path: "/sitecore/content/home") {
id
name
template {
id
name
}
}
}
Now we can see the home item is based on the Sample Item
template.
{
"data": {
"item": {
"id": "110D559FDEA542EA9C1C8A5DF7E70EF9",
"name": "Home",
"template": {
"id": "76036F5ECBCE46D1AF0A4143F9B557AA",
"name": "Sample Item"
}
}
}
}
That right there is the power of GraphQL. To query the template used by the item you don't have to send another request for a different resource. You simply update the query to link to the linked resource and extract the properties of it that you want.
To access a single field, you can use the field
field of the Item
type. This will return an ItemField
type:
query HomeItem {
item(language: "en", path: "/sitecore/content/home") {
id
name
field(name:"title") {
name
value
}
}
}
And here's the response which includes the field requested.
{
"data": {
"item": {
"id": "110D559FDEA542EA9C1C8A5DF7E70EF9",
"name": "Home",
"field": {
"name": "Title",
"value": "Sitecore Experience Platform"
}
}
}
}
To access multiple fields, you can include the field
field multiple times. However, if the field is repeated you'll get an error when the query is executed because there are multiple output properties with the same name at the same level. So when accessing multiple fields you need to use aliases to give the output a different name.
query HomeItem {
item(language: "en", path: "/sitecore/content/home") {
id
name
title: field(name:"title") {
value
}
text: field(name:"text") {
value
}
}
}
Now the fields in the output use the alias we provided, which looks much cleaner.
{
"data": {
"item": {
"id": "110D559FDEA542EA9C1C8A5DF7E70EF9",
"name": "Home",
"title": {
"value": "Sitecore Experience Platform"
},
"text": {
"value": "<p style=\"line-height: 22px;\">From a single connected platform that also integrates
with other customer-facing platforms, to a single view of the customer in a big data marketing
repository, to completely eliminating much of the complexity that has previously held marketers
back, the latest version of Sitecore makes customer experience highly achievable. Learn how the
latest version of Sitecore gives marketers the complete data, integrated tools, and automation
capabilities to engage customers throughout an iterative lifecycle – the technology
foundation absolutely necessary to win customers for life.</p>\n<p>For further information,
please go to the <a href=\"https://doc.sitecore.net/\" target=\"_blank\" title=\"Sitecore
Documentation site\">Sitecore Documentation site</a></p>\r"
}
}
}
}
You could also use an alias in the first field example above, to make the output cleaner.
When writing queries for your application, you'll know which fields you want to access. But when you're initially creating the queries you might need to explore a little. This is where the fields
field (note it's multiple fields) can help. It enumerates all fields of the item allowing you to discover what fields the item has.
query HomeItem {
item(language: "en", path: "/sitecore/content/home") {
id
name
fields {
name
value
}
}
}
The response includes all the fields now.
{
"data": {
"item": {
"id": "110D559FDEA542EA9C1C8A5DF7E70EF9",
"name": "Home",
"fields": [
{
"name": "Text",
"value": "<p style=\"line-height: 22px;\">From a single connected platform that also integrates
with other customer-facing platforms, to a single view of the customer in a big data marketing
repository, to completely eliminating much of the complexity that has previously held marketers
back, the latest version of Sitecore makes customer experience highly achievable. Learn how the
latest version of Sitecore gives marketers the complete data, integrated tools, and automation
capabilities to engage customers throughout an iterative lifecycle – the technology
foundation absolutely necessary to win customers for life.</p>\n<p>For further information,
please go to the <a href=\"https://doc.sitecore.net/\" target=\"_blank\" title=\"Sitecore
Documentation site\">Sitecore Documentation site</a></p>\r"
},
{
"name": "Title",
"value": "Sitecore Experience Platform"
}
]
}
}
}
Yeah, it's the same fields we've already been looking at above, but I didn't need to specify each field individually. Let's try doing that on something that uses a template other than the Sample Item
template.
query MvpHomeItem {
item(language: "en", path: "/sitecore/content/MvpSite/Home") {
id
name
template {
id
name
}
fields {
name
value
}
}
}
This temlpate includes many more fields than the Sample Item
template.
{
"data": {
"item": {
"id": "94DE9AC3A9F740ABAE90ACDA364B9C40",
"name": "Home",
"template": {
"id": "9C1E30A086914AD4BEF0E1F446EEF8F3",
"name": "Homepage"
},
"fields": [
{
"name": "Logo SVG path",
"value": "/images/sitecore.svg"
},
{
"name": "IncludeInMenu",
"value": ""
},
{
"name": "MenuTitle",
"value": "Home"
},
{
"name": "RequiresAuthentication",
"value": ""
},
{
"name": "MetaDescription",
"value": "MVP Home Description"
},
{
"name": "MetaKeywords",
"value": "MVP, Most Valuable Professional"
},
{
"name": "OgTitle",
"value": "Home"
},
{
"name": "OgImage",
"value": "<image mediaid=\"{1023987B-1EAD-49A0-BF57-9A1C90A0F88A}\" />"
},
{
"name": "OgType",
"value": "article"
},
{
"name": "OgDescription",
"value": "MVP Home Description"
}
]
}
}
}
Concrete Types
So far we've been using the Item
type in the query, which is actually an interface. Being it covers all kinds of items (all templates) the field names are quite generic. The Experience Edge schema for an XM tenant also includes concrete types for each template, all implementing the Item
interface. Concrete types make the query even cleaner by exposing fields with names which correspond to the fields of the template.
Additionally, the type used for fields in the previous queries is ItemField
which is also an interface. Just like with items and templates, there are concrete implementations of ItemField
for each kind of field. Concrete field types expose the data of the field in more useable ways than just the raw string values we've seen so far. For example, a LookupField
allows querying through to the item selected.
To use the concrete types, we must use an inline fragment so we can specify the fields when the concrete type of the object is the type of the fragment. Let's start with using concrete field types.
query MvpHomeItem {
item(language: "en", path: "/sitecore/content/MvpSite/Home") {
id
name
title: field(name:"ogtitle") {
... on TextField {
value
}
}
image: field(name:"ogimage") {
... on ImageField {
name
mimeType
size
alt
src(maxHeight:100 maxWidth:200)
}
}
}
}
{
"data": {
"item": {
"id": "94DE9AC3A9F740ABAE90ACDA364B9C40",
"name": "Home",
"title": {
"value": "Home"
},
"image": {
"name": "OgImage",
"mimeType": "image/jpeg",
"size": 59918,
"alt": "Sitecore Most Valuable Professional",
"src": "https://edge.sitecorecloud.io/sitecoresaa94c3-xmcloudintr2ef7-production-9f57/media/Project/
MvpSite/Sitecore-MVP-logo.jpg?mw=200&mh=100"
}
}
}
}
Using the ItemField
type we're now linking through to the media item which the image has selected, and extracting values from that media like the alt text.
Now let's try using a concrete Item
type.
query MvpHomeItem {
item(language: "en", path: "/sitecore/content/MvpSite/Home") {
... on Homepage {
id
name
ogTitle {
value
}
ogImage {
name
mimeType
size
alt
src(maxHeight: 100, maxWidth: 200)
}
}
}
}
See how the fields are now named after the template field? The types of those fields are also the concrete type and not the ItemField
template, so we don't need to use inline fragments when using them.
{
"data": {
"item": {
"id": "94DE9AC3A9F740ABAE90ACDA364B9C40",
"name": "Home",
"ogTitle": {
"value": "Home"
},
"ogImage": {
"name": "OgImage",
"mimeType": "image/jpeg",
"size": 59918,
"alt": "Sitecore Most Valuable Professional",
"src": "https://edge.sitecorecloud.io/sitecoresaa94c3-xmcloudintr2ef7-production-9f57/media/Project/
MvpSite/Sitecore-MVP-logo.jpg?mw=200&mh=100"
}
}
}
}
And the response is exactly like the previous example. We're still accessing the exact same data, just in a nicer "strongly-typed" fashion.
Sometimes it can be a bit difficult to work out what type something is. To help with discovery we can use some introspection to find what type an object is.
query MvpHomeItem {
item(language: "en", path: "/sitecore/content/MvpSite/Home") {
__typename
title: field(name:"ogtitle") {
__typename
}
image: field(name:"ogimage") {
__typename
}
}
}
__typename
is part of the GraphQL introspection features and exposes the name of the type of the object.
{
"data": {
"item": {
"__typename": "Homepage",
"title": {
"__typename": "TextField"
},
"image": {
"__typename": "ImageField"
}
}
}
}
Navigating the content tree
The Item
interface contains a couple of fields which can be used to navigate around the content tree, from a given item.
ancestors
provides access to all the ancestor items of the current item.children
provides access to all the children of the current item.hasChildren
indicates whether the current item has any children.parent
provides access to the parent of the current item.
All the above fields expose different types which are more appropriate to the field being accessed. For example parent
exposes an Item
type whereas ancestors
exposes an array of Item
.
query MvpHomeItem {
item(language: "en", path: "/sitecore/content/MvpSite/Home") {
...itemInfo
ancestors {
...itemInfo
}
parent {
...itemInfo
}
hasChildren
children {
results {
...itemInfo
}
}
}
}
fragment itemInfo on Item {
id
name
}
{
"data": {
"item": {
"id": "94DE9AC3A9F740ABAE90ACDA364B9C40",
"name": "Home",
"ancestors": [
{
"id": "A43C669260F44743A1BE6424DE611DFA",
"name": "MvpSite"
},
{
"id": "0DE95AE441AB4D019EB067441B7C2450",
"name": "content"
},
{
"id": "11111111111111111111111111111111",
"name": "sitecore"
}
],
"parent": {
"id": "A43C669260F44743A1BE6424DE611DFA",
"name": "MvpSite"
},
"hasChildren": true,
"children": {
"results": [
{
"id": "FE6EC725417344C5A2BC8C92CF7E74B5",
"name": "404"
},
{
"id": "0D97B45DC5894495A4959AAFF4FBD2C3",
"name": "About"
},
{
"id": "0068B8802204475995190ED217BBC156",
"name": "Application"
},
{
"id": "FA04808614BC4E7D998759F8B4ADC286",
"name": "Become-an-mvp"
},
{
"id": "F96C13C3E71B4DA2881F17F4DCAFE56F",
"name": "Benefits"
},
{
"id": "C59D92D6739B4424BF07D3AD7C2B9FC1",
"name": "Contact"
},
{
"id": "67BA86B64ADF422BBD311C17336C5FBF",
"name": "Directory"
},
{
"id": "CAFD6A4EF1C748BF96FA8689193C8B55",
"name": "Mentor-Program"
},
{
"id": "CBF43178A0934BAE8CE072D3DF81D77B",
"name": "Podcast"
},
{
"id": "728D5B0E573F4EB682339A5DC958507C",
"name": "Thank-you"
}
]
}
}
}
}
You can also navigate the content tree just by manipulating the path
argument when using the item
query, a long as you know the paths you want to access.
The Layout Query
If you're using XM, then you're likely building websites and have a URL structure which resolves to items. The layout
query allows resolving items from routes rather than fully qualified Sitecore paths. Let's find the item which is used for the /About
route on the MVP site.
query AboutPage {
layout(language:"en" site:"mvp-site" routePath:"/about") {
item {
id
name
path
}
}
}
{
"data": {
"layout": {
"item": {
"id": "0D97B45DC5894495A4959AAFF4FBD2C3",
"name": "About",
"path": "/sitecore/content/MvpSite/Home/About"
}
}
}
}
The layout
query returns a LayoutData
type which only has a single field named item
, which links to the item for the route provided.
The Search Query
The search
query allows searching across all items instead of having to resolve them from paths or routes. For example, let's search for all items based on the Page
template.
query Pages {
search(
first: 5
where: { name: "_templates", value: "A054184C3B204C2BAB04C8A03ACD5522" }
orderBy: { name: "_name", direction: ASC }
) {
pageInfo {
hasNext
endCursor
}
results {
id
name
path
}
}
}
The response includes the first page of results. I was expecting quite a lot of results, so I've used the first
argument on the search
query to limit the number of results to 5, and also used the orderBy
argument so the results are ordered by name.
{
"data": {
"search": {
"pageInfo": {
"hasNext": true,
"endCursor": "eyJzZWFyY2hBZnRlciI6WyJhZG1pbiIsIkI3Qjk0M0M0NTU1RDQwRTE5Njc3QzY4NzI2Njg2M
TE5IiwiQjdCOTQzQzQ1NTVENDBFMTk2NzdDNjg3MjY2ODYxMTkiXSwiY291bnQiOjV9"
},
"results": [
{
"id": "9AC6DDA387854C63A2AB457D8CC56D19",
"name": "$name",
"path": "/sitecore/templates/Branches/Project/MvpSite/Page/$name"
},
{
"id": "FE6EC725417344C5A2BC8C92CF7E74B5",
"name": "404",
"path": "/sitecore/content/MvpSite/Home/404"
},
{
"id": "9C05E62330314A61B75F34E5EDB92DA0",
"name": "__Standard Values",
"path": "/sitecore/templates/Project/MvpSite/Page/__Standard Values"
},
{
"id": "0D97B45DC5894495A4959AAFF4FBD2C3",
"name": "About",
"path": "/sitecore/content/MvpSite/Home/About"
},
{
"id": "B7B943C4555D40E19677C68726686119",
"name": "Admin",
"path": "/sitecore/content/MvpSite/Home/Application/Admin"
}
]
}
}
}
The where
argument can also be made more complex by combining multiple ItemSearchPredicateInput
types using AND
and OR
. For example, I could search for all items based on the Page
template that have the field IncludeInMenu
set to 1
:
query Pages {
search(
first: 5
where: {
AND: [
{ name: "_templates", value: "A054184C3B204C2BAB04C8A03ACD5522" }
{ name: "IncludeInMenu", value: "1" }
]
}
orderBy: { name: "_name", direction: ASC }
) {
pageInfo {
hasNext
endCursor
}
results {
id
name
path
}
}
}
Now only items that match both predicates are returned:
{
"data": {
"search": {
"pageInfo": {
"hasNext": true,
"endCursor": "eyJzZWFyY2hBZnRlciI6WyJhcHBseSIsIkIzRDM2OTg4QjRBRTQ5MTVBNkNGRjUxMzNCRTRFM
TAwIiwiQjNEMzY5ODhCNEFFNDkxNUE2Q0ZGNTEzM0JFNEUxMDAiXSwiY291bnQiOjV9"
},
"results": [
{
"id": "9AC6DDA387854C63A2AB457D8CC56D19",
"name": "$name",
"path": "/sitecore/templates/Branches/Project/MvpSite/Page/$name"
},
{
"id": "9C05E62330314A61B75F34E5EDB92DA0",
"name": "__Standard Values",
"path": "/sitecore/templates/Project/MvpSite/Page/__Standard Values"
},
{
"id": "0D97B45DC5894495A4959AAFF4FBD2C3",
"name": "About",
"path": "/sitecore/content/MvpSite/Home/About"
},
{
"id": "0068B8802204475995190ED217BBC156",
"name": "Application",
"path": "/sitecore/content/MvpSite/Home/Application"
},
{
"id": "B3D36988B4AE4915A6CFF5133BE4E100",
"name": "Apply",
"path": "/sitecore/content/MvpSite/Home/Application/Apply"
}
]
}
}
}
The name
field of a ItemSearchPredicateInput
type can include any field of the item, as well as these predefined fields:
_name
for the item name._path
for the item path._parent
for the item parent IDs._templates
for the item template ID or any base template ID in the template hierarchy._hasLayout
to check if the item has layout defined._language
for the language of the item.
These predefined fields are documented at https://doc.sitecore.com/xp/en/developers/hd/210/sitecore-headless-development/the-experience-edge-schema.html#available-fields-for-content-search.
The Sites Query
This is a new query type made available with the XM Edge connector version 21 and later. It allows querying all defined sites for the tenant and accessing detailed information for the site, such as error handling information, the root path of the site in the content tree or several other pieces.
Firstly, let's discover all the sites in this Edge tenant:
query Sites {
site {
siteInfoCollection {
name
}
}
}
This will list all sites.
{
"data": {
"site": {
"siteInfoCollection": [
{
"name": "sugcon-anz"
},
{
"name": "sugconeu"
},
{
"name": "sugcon-eu"
},
{
"name": "website"
},
{
"name": "mvp-site"
},
{
"name": "sugconanz"
}
]
}
}
}
I can also query directly for a single site, if I already have it's name:
query Sites {
site {
siteInfo(site: "mvp-site") {
rootPath
routes(language: "en", first: 5) {
pageInfo {
hasNext
endCursor
}
results {
routePath
}
}
}
}
}
This query will return a paged list of the routes for the mvp-site
site.
{
"data": {
"site": {
"siteInfo": {
"rootPath": "/sitecore/content/MvpSite",
"routes": {
"pageInfo": {
"hasNext": true,
"endCursor": "eyJzZWFyY2hBZnRlciI6WzE2NjI1NjMzMDAwMDAsIkJBOTVCOERGRTQyMzRDMUVCQTNEN
DA2NDg5RjIzQUY2IiwiQkE5NUI4REZFNDIzNEMxRUJBM0Q0MDY0ODlGMjNBRjYiXSwiY291bnQiOjV9"
},
"results": [
{
"routePath": "/Application/Admin/Roles/New"
},
{
"routePath": "/Application/My-Data"
},
{
"routePath": "/Application/Apply"
},
{
"routePath": "/Application/Admin/Mvp-Types"
},
{
"routePath": "/Application/Admin/Users/Edit"
}
]
}
}
}
}
}
Reference
If you'd like to see some queries from a real XM headless implementation, the source code for the sites mentioned at the top of this post is available on github at https://github.com/Sitecore/XM-Cloud-Introduction.
There are also more example queries for XM tenants in the documentation at https://doc.sitecore.com/xp/en/developers/hd/210/sitecore-headless-development/query-examples.html.
Leave a comment