codeflood logo

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.

home-item.graphql
1
2
3
4
5
6
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.

home-item-response.json
1
2
3
4
5
6
7
8
{
"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.

home-item-template.graphql
1
2
3
4
5
6
7
8
9
10
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.

home-item-template-response.json
1
2
3
4
5
6
7
8
9
10
11
12
{
"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:

item-field.graphql
1
2
3
4
5
6
7
8
9
10
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.

item-field-response.json
1
2
3
4
5
6
7
8
9
10
11
12
{
"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.

item-multiple-fields.graphql
1
2
3
4
5
6
7
8
9
10
11
12
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.

item-multiple-fields-response.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"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 &ndash; 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.

item-fields.graphql
1
2
3
4
5
6
7
8
9
10
query HomeItem {
item(language: "en", path: "/sitecore/content/home") {
id
name
fields {
name
value
}
}
}

The response includes all the fields now.

item-fields-response.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"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 &ndash; 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.

mvphome-fields.graphql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.

mvphome-fields-response.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
{
"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.

mvphome-concrete-fields.graphql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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)
}
}
}
}
mvphome-concrete-fields-response.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"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.

mvphome-concrete-item.graphql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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.

mvphome-concrete-item-response.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"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.

introspection.graphql
1
2
3
4
5
6
7
8
9
10
11
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.

introspection-response.json
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"data": {
"item": {
"__typename": "Homepage",
"title": {
"__typename": "TextField"
},
"image": {
"__typename": "ImageField"
}
}
}
}

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.

content-tree.graphql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
}
content-tree-response.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
{
"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.

content-tree.graphql
1
2
3
4
5
6
7
8
9
query AboutPage {
layout(language:"en" site:"mvp-site" routePath:"/about") {
item {
id
name
path
}
}
}
content-tree-response.json
1
2
3
4
5
6
7
8
9
10
11
{
"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.

pages.graphql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.

pages-response.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"data": {
"search": {
"pageInfo": {
"hasNext": true,
"endCursor": "eyJzZWFyY2hBZnRlciI6WyJhZG1pbiIsIkI3Qjk0M0M0NTU1RDQwRTE5Njc3QzY4NzI2Njg2MTE5IiwiQjdCOTQ
zQzQ1NTVENDBFMTk2NzdDNjg3MjY2ODYxMTkiXSwiY291bnQiOjV9"
},
"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:

where.graphql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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:

where-response.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"data": {
"search": {
"pageInfo": {
"hasNext": true,
"endCursor": "eyJzZWFyY2hBZnRlciI6WyJhcHBseSIsIkIzRDM2OTg4QjRBRTQ5MTVBNkNGRjUxMzNCRTRFMTAwIiwiQjNEMzY5ODhCN
EFFNDkxNUE2Q0ZGNTEzM0JFNEUxMDAiXSwiY291bnQiOjV9"
},
"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:

sites.graphql
1
2
3
4
5
6
7
query Sites {
site {
siteInfoCollection {
name
}
}
}

This will list all sites.

sites-response.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{
"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:

site.graphql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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.

site-response.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
{
"data": {
"site": {
"siteInfo": {
"rootPath": "/sitecore/content/MvpSite",
"routes": {
"pageInfo": {
"hasNext": true,
"endCursor": "eyJzZWFyY2hBZnRlciI6WzE2NjI1NjMzMDAwMDAsIkJBOTVCOERGRTQyMzRDMUVCQTNENDA2NDg5RjIzQUY2IiwiQkE5NU
I4REZFNDIzNEMxRUJBM0Q0MDY0ODlGMjNBRjYiXSwiY291bnQiOjV9"
},
"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.

Comments

Comments are closed

loading...

Leave a comment

All fields are required.