XM Developer Guide
This document provides details on the architecture, configuration, and troubleshooting of fabric Experience Manager (XM).
Overview
Experience Manager (XM) is a Content Management System (CMS) with a web-based editor for content creation and layout. You can also preview your page, create multiple page versions, and schedule your content for publication.
-
Pages
- An XM Page is your artboard to lay out and populate content using components such as section titles, product image carousels, and description boxes. Pages can be edited, versioned, scheduled for publication, and archived.
-
Global Elements
- Global elements are page components such as headers and footers that are standardized across your site. As a frontend developer, you will create a descriptor file for each element and they will appear on the Global Element page when available.
-
Menus
- Menus are custom navigation paths that can be quickly modified. You can make use of parent and child categories to create multiple navigation paths and multichannel offerings using a configurable hierarchical structure.
XM Architecture
Click here to view the Knowledge Base articles on XM
Prerequisite
Copilot is the primary interface for working with fabric applications. To access Copilot, you need an active account with an account ID. Contact your Copilot Admin or Customer Success Representative to obtain the required credentials.
In addition, XM must be connected to a front end such as a web storefront or mobile app to display content and facilitate interaction.
Tech Stack
- Frontend: React / Single Spa
- Backend: Java / Micronaut
- Javascript SDK: @teamfabric/xpm
- Testing Frameworks:
- Styling
More Information
Getting Started
The following articles provide the steps required to configure XM.
Setting up Storefront for XM integration
NPM Package Installation
Before installing the NPM package, you must get an NPM token from the fabric Onboarding team and save it in the .npmrc
file in the root directory of your project. Your .npmrc
file should look like this:
//registry.npmjs.org/:_authToken=AUTH_TOKEN_HERE
To install the NPM package into your repository: npm install --save @teamfabric/xpm
Note: For the Live Preview functionality, we currently support only React with OOTB React component functionality. For ease of implementation, we provide an NPM package called @teamfabric/xpm.
XM Components
An XM Component is a JSON object that provides a schema that a content writer can populate with static content in the Copilot XM UI, and populate the schema to the storefront for display.
Note: XM Components do not return HTML content (except for RichText types). They return mapped JSON schemas and content so that the storefront can display these components as they see fit.
The descriptor files describe the components you want XM to use. The components can be global components (that you want to appear across your site) or page-specific components (such as Product Carousel). These files also describe the data attributes and data types of those attributes.
Creating an XM Component with a descriptor file
See the steps above to install an NPM package.
-
Install XM SDK:
npm install --save @teamfabric/xpm
Note : If you get an error, contact the Customer Success Representative to get a valid NPM token to add into the .npmrc file. - Create a folder in your repository to house the component and its relevant files (tests, styles, etc).
-
In this folder, create a file named
descriptor.js or descriptor.ts
. -
Use the
Types
component from the@teamfabric/xpm
npm package to create a custom descriptor object. This JSON object should include the following:-
id
: The component ID. It should be the same as the corresponding React component. -
label
: A human-readable name of the component to be displayed in the Copilot XM UI. -
description
: A description of the component. -
attributes
: An object with custom attributes associated with the component. You may add as many attributes to a component as required. The attribute names are customizable. In addition:- An attribute can have more than one type. Refer to the Available Types section for details.
-
The
label
field is mandatory for the attribute to display a human-readable name in the XM UI. -
An attribute can have an optional
default
field that will automatically display in the XM preview. This default value can be overwritten via the XM UI.
-
Sample descriptor file
import { Types } from '@teamfabric/xpm';
export default Types.Component({
id: 'ExampleComponent',
label: 'Example Component',
description: 'An example component in XM',
attributes: {
title: Types.String({
label: 'Title',
}),
description: Types.MultiLineString({
label: 'Description',
default: 'A default description here'
}),
color: Types.Enum({
label:
'Color',
children: ['black', 'white', 'gray'],
default: 'black'
}),
image: Types.Image({
label: 'Image',
url: Types.String({ label: 'URL' }),
altText: Types.MultiLineString({ label: 'Alt text' })
})
}
});
Note: This is how the ExampleComponent
descriptor schema appears in the XM UI.
Defining the component
Place the file of your React component in the same folder as your descriptor file (in this case, <root directory/atoms/<name_of_your_component>/
), and define it accordingly. Here is a sample:
import React from 'react';
const ExampleComponent = (props) => {
const { title, description, color, image } = props;
const { altText, url } = image;
return (
<div>
<h1>{title}</h1>
<p style={{ color: color }}>{description}</p>
<img src={url} alt={altText} />
</div>
);
};
export default ExampleComponent;
Note:
-
The
props
passed into this component map to the attributes that you describe in your descriptor file. - This component can be customized with any styling, layouts, animations, etc.
Define a file called cms-components
in your root directory and import your React component. Create an object called componentsById
that is exported from the file, and add the React component, as follows:
import ExampleComponent from 'atoms/ExampleComponent';
// Import more components here as needed
export const componentsById = {
ExampleComponent: ExampleComponent
// Add more here components here as needed
};
Note: Ensure you have followed the steps to enable the XM Preview.
The following image shows how the previously-mentioned example renders on the XM preview:
Nested Components
XM uses Components to map static data to a schema so that it can be displayed on your storefront.
export default Types.Component({
id: 'ExampleNestedComponent',
label: 'Example Nested Component',
description: 'An example nested component',
allowNestedComponents: true, // this tells xm to allow nested components
attributes: {
...
},
})
XM saves the nested components in the components
field of the main component’s params. The main component renders the nested components by using componentsById
with the params provided.
id: "ExampleNestedComponent",
key: "<some key>",
label: "Example Nested Component",
order: 4,
params: {
...
components: [{…}],
}
Types
Use the Types component from @teamfabric/xpm
to define the schema for your descriptors.
To use the Types component, import it at the top of your js/ts file:
import { Types } from '@teamfabric/xpm'
Available Types
The following types are available in the @teamfabric/xpm package.
- String
- Boolean
- Enum
- Array
- Shape
- Number
- MultilineString
- RichText
- Image
Types.Shapes
cannot exist independently, it can appear only inside the Types.Array
.
Types.Array
always needs a Types.Shapes
to be present as a type for its sub-attributes.
1. components: Types.Array({
2. name: 'Image Urls',
3. isGlobal: false, // make this true if you want this component to be a Global component
4. children: Types.Shape({
5. children: {
6. name: Types.String({ label: 'url' }),
7. }
8. })
9. })
No two attributes should have the same name when they are at the same level of the hierarchy.
The CMS components built in your code must not have a duplicate name or id.
Setting isGlobal attribute as true
makes the component a global component.
- A descriptor should have the isGlobal attribute when it is for a Global Element , section of your website that are standardized across multiple pages, like headers and footers
-
When the
isGlobal
attribute is
false
, the descriptor and its component can be used for a Page only.
Enable XM Preview
XM expects the storefront to provide three routes on its server:
-
/cms/preview
-
/cms/preview-gc
-
/_descriptors.json
.
XM requires these routes to preview a page (for /cms/preview
), preview a global component (/cms/preview-gc
), and fetch the components that XM should display as options (/_descriptors.json
).
Enable Preview
The Preview component from @teamfabric/xpm powers the Preview feature of XM. The preview component injects the content defined on XM into UI components in real time.
To use this component, import it at the top of your js/ts file:
import { Preview } from '@teamfabric/xpm'
Naming Components
It is recommend to create a file called cms-components
in the root folder of your project. This is to map the ID of an XM Component to the ID of the React component that will render the XM Component’s data with HTML/CSS. For example, if you want to create a component called ProductCarousel, ensure the ID of the descriptor file for this component is called ExampleComponent
and that your component has the same name ExampleComponent
.
Sample:
// components.js
import ExampleComponent from 'atoms/ExampleComponent';
export const componentsById = {
ExampleComponent
};
The componentsById
object is described below.
/cms/preview
Route
The /cms/preview
route should use the Preview component provided by the @teamfabric/xpm. In this example below, the global components (such as header and footer) are imported to preview your page with the global components.
import { Preview } from '@teamfabric/xpm';
import Header from 'components/header'; // YOUR GLOBAL COMPONENTS
import Footer from 'components/footer'; // YOUR GLOBAL COMPONENTS
import { componentsById } from 'cms-components';
import { FABRIC_API_URL, FABRIC_ACCOUNT, FABRIC_CHANNEL, FABRIC_STAGE } from 'your-config-or-environment';
const PreviewPage = ({ headerProps, footerProps }) => {
return (
<>
<Header {...headerProps.params} />
<div className="app-content">
<Preview componentsById={componentsById} />
</div>
<Footer {...footerProps.params} />
</>
);
};
PreviewPage.getInitialProps = async () => {
// Fetch global components to render on the preview
const response = await fetch(`${FABRIC_API_URL}/api-xpm/global-component/live`, {
method: 'get',
headers: {
'x-site-context': JSON.stringify({ account: FABRIC_ACCOUNT, channel: FABRIC_CHANNEL, stage: FABRIC_STAGE })
}
})
const gcData = await response.json()
// Retrieve props of the global components
const headerProps = (gcData && gcData.find((item) => item.id == 'Header')) || {}
const footerProps = (gcData && gcData.find((item) => item.id == 'Footer')) || {}
return { headerProps, footerProps }
}
export default PreviewPage;
The componentsById
object on line 12 maps the ID of the descriptor to the ID of the React component.
Note: It is mandatory these IDs match.
/cms/preview-gc
Route
The /cms/preview-gc
route renders the global components.
import { Preview } from '@teamfabric/xpm'
import { componentsById } from 'cms-components'
export default () => (
<>
<Preview componentsById={componentsById} />
</>
);
Note: If you are using an analytics tool, we highly recommend adding explicit exclusions of tracking the /cms/preview
and /cms/preview-gc
routes to avoid confusing non-technical stakeholders.
/_descriptors.json
Route
XM uses the /_descriptors.json
route to display all of the components as options on the Preview pane. This route should return a JSON object of all of your descriptor files' contents combined into one.
Place this script in the lib/xm/combine-descriptors.js
file in your project’s root, and ensure that every descriptor file is named descriptor.js
or descriptor.ts
. You can update this script with your own custom directory structure.
This script does the combination work for you.
Sample
// 'esm' allows node.js to require es6+ modules (import/export syntax)
// Used in descriptor files
require = require('esm')(module);
const path = require('path');
const glob = require('glob');
const fs = require('fs');
(function buildDescriptors() {
const root = path.resolve(__dirname, '../../');
let descriptors = glob
.sync('{components,modules}/**/descriptor.{js,ts}', { cwd: root })
.map((fname) => require(path.join(root, fname)).default)
.sort((a, b) => {
if (a.label < b.label) {
return -1;
}
if (a.label > b.label) {
return 1;
}
return 0;
});
descriptors = JSON.stringify(descriptors);
fs.writeFile('<ROOT_DIR>/_descriptors.json', descriptors, function (err) {
if (err) throw err;
});
})();
Note: The location of the descriptor files doesn’t matter, since the script below only checks for the naming of the files in the components
and modules
folders.
Add a command in your package.json
file called descriptors
to update the _descriptors.json
file whenever you add or update a component’s descriptor file.
"scripts": {
...
"descriptors": "node lib/xm/combine-descriptors.js",
...
}
iframes
XM utilizes iframes for its preview functionality. Therefore, it is imperative that the storefront’s headers do not explicitly deny the application to be loaded in iframe. For example, if the storefront has the header X-Frame-Options
set to Deny
, the application will not load in XM’s preview.
If headers need to be explicitly defined, use the Content-Security-Policy
with the value: frame-ancestors 'self' '<env>.copilot.fabric.inc'
so that XM can load your application in its preview iframe.
Calling XM APIs from Storefront
Storefront consumes XM components via REST APIs, which returns a JSON object with the attributes defined in the descriptor files mapped against the static content.
API URL
The Customer Success Representative will provide you a commerce URL to call the XM APIs from your storefront.
Sample URL: https://<STAGE>-apigw.<CUSTOMER_NAME>.fabric.zone/api-xpm/v2/pages/live
Note: Do not use the Copilot fabric URL https://<ENV>.copilot.fabric.inc
on your storefront.
API Headers
Use the following API header for every API call to XM:
x-site-context
: An object containing the stage
, account
, and channel
information.
Sample:
x-site-context: {
"channel": YOUR_CHANNEL,
"stage":"YOUR_STAGE",
"account":"YOUR_ACCOUNT_ID"
}
-
channel
: The channel where the information is displayed (default: 12
) -
stage
: The environment where the request is made (sandbox
orlive
) -
account
: The account ID.
Finding your account ID
- Log in to Copilot
- Navigate to XM
- Open the Inspector
- Navigate to Application
-
Click
Session Storage
and find the
account
value.
Note:
-
The header can also be called
X-Site-Context
. -
Several fabric products require the
date
field in the header. However, we recommend you not add the
date
field to the
x-site-context
header of the XM APIs because it may result in a cache miss and increase the latency of your API calls to XM.
API List
Integrating SEO data
Click here for instructions on defining SEO data for a Page. Once you have defined the SEO data for a page, you must reference it in your storefront. When loading a page, use the GET /v2/page/live endpoint and look for the custom-defined SEO data in the response.
Sample:
{
...
data: {
page: {
...
seoFields: {
title: 'Title of SEO field',
description: 'Description of SEO field',
metadata: [
// Custom metadata of SEO field
...
]
}
}
}
...
}
Click here for the full API schema
We recommend you make a separate component that will render the SEO information in the head of each page. Below is a recommended approach using Typescript. This implementation is fully customizable.
const SeoHead = ({ seoFields, pageInfo, layoutData }: SeoHeadProps) => {
const { title, description, metadata = [] } = seoFields ?? {};
return (
<head>
<title>{title}</title>
<meta charSet="utf-8" />
<meta name="title" content={title} />
<meta name="description" content={description} />
{metadata?.map((meta) => {
const { name, content } = meta;
if (name && content) return <meta name={name} content={content} key={name} />;
})}
</head>
);
};
XM Troubleshooting
- My preview is blank
- My custom components are not rendering in my XM editor
- The multichannel feature is not working as expected
- I see the wrong Page Version on my website
- I’m getting an error when calling the XM APIs: @teamfabric/route: Error: apiUrl is required for @teamfabric/request module.
My preview is blank
Reproduce the scenario:
- Log in to your XM Copilot instance.
- Open the page version in the XM editor.
- Confirm whether the preview is rendering any content.
-
If there is a preview:
- Is content available in the preview?
- Are Global Elements the only components (header, footer, etc.)?
-
If yes, preview route in the storefront’s server has not recieved the content in the sidebar, so check your
/cms/preview
or/cms/preview-gc
routes.
-
If there is no preview:
- Ensure that your XM instance is configured with an ecommerce app URL. Refer to Configuring XM for instructions
-
Ensure that your
/cms/preview
and/cms/preview-gc
routes are defined. Refer to: Enable XM Preview . -
Ensure that your
_descriptors.json
route is defined with all of your descriptors, properly formatted (nonull
orundefined
objects). -
Ensure that there are no issues arising from your storefront code:
- Are there any errors being thrown by the storefront?
- Is the CSS of this page hindering any rendering of a component?
My custom components are not rendering in my XM editor
-
XM has a cache TTL of ~15 minutes on the
_descriptors.json
route of your storefront ( XM Cache Details ). If changes are not reflected after this TTL, ensure your storefront is not caching these details as well.-
Ensure that your
_descriptors.json
file with all of your descriptors is defined and publicly accessible on the<storefront-url>/_descriptors.json
route. It must also be properly formatted (nonull
orundefined
objects).
-
Ensure that your
The multichannel feature is not working as expected
-
The multichannel feature flag must be enabled on your XM configuration. Follow the steps here
How to: Configure XM
and specify the feature flag
multiChannel
:true
in thefeatures
object. - If you get an error, create a fabric support ticket .
I see the wrong Page Version on my website
-
What is the expected Page and its Version on your website? Which URL is mapped to that?
-
Is the page version in LIVE state in XM?
- If no (the status of the page is DRAFT, ENDED, or SCHEDULED) then this is the expected behavior. You will need to publish the page from within the XM editor to resolve this issue.
- If yes, proceed to the next step.
-
Verify the URL are you providing in the
API call
.
-
Is the query parameter value the same as that of the page?
- The URL must match the URL of the Page or the page Version being queried, which you can verify from the XM editor view version details .
-
Is the query parameter value the same as that of the page?
- If the URL matches and the page is LIVE, you may report it as a bug by opening a fabric support ticket . The Engineering team will investigate and fix the issue.
-
Is the page version in LIVE state in XM?
I’m getting an error when calling the XM APIs: @teamfabric/route: Error: apiUrl is required for @teamfabric/request module.
- This error indicates your environment is not set up properly to ‘discover’ the XM service. Please create a fabric support ticket , select the Environments, and specify the issue (the environment, the account, etc.).
Fallback Strategies
Deactivated, Archived, or Deleted content is not available to online visitors and will result in a broken user experience. It is recommended that if XM returns a 404 – Page Not Found error for a Page, your site explicitly shows the 404 – Page Not Found, or redirects the user to an existing page (such as the homepage).
If XM returns a 404 – Not Found error for a Global Element **or Menu, it is recommended that your site goes to a default version of the **Global Element (example - header, footer, etc.) or Menu. This way, it is evident to you that the content must be addressed, but the shopper is not inconvenienced with a broken user experience.
XM UI States
The following are supported XM states (or statuses).
Active | The content is live, was recently live, or will be live. Active content shows in the main content view tables. |
Inactive | The content is not live and should not be available to online visitors. Inactive content shows in the main content view tables and they can be easily reactivated for quick publishing. |
Live | The content is available to online visitors. Live is a subset of the Active state. |
Scheduled | The content will be made available to online visitors on a specific date. Scheduled is subset of the Active state. |
Ended | The content is replaced by a Scheduled content. The Ended content can be in an Active or Inactive state. |
Draft (Active or Inactive) | The content is work-in-progress and should not be available to online visitors. Drafts can be in Active or Inactive states. |
Archived | The content is no longer required, and can be removed to declutter the list of pages. This content will be available in the Archived section for future reference, and can be unarchived if necessary. |
XM Cache Details
This section explains the caching details for the different actions in XM and identifies possible reasons why content is not updating on your storefront.
Storefront scenarios
The cache is 60 seconds for the following scenarios:
- Publishing a new page version for the first time.
- Making changes to a live page version.
- Publishing a new page version when one is already live.
- Scheduling a new page version when one is already live.
- Scheduling a new page version to be live.
- Changes to the Preview routes (/cms/preview and /cms/preview-gc).
Any changes will take up to a minute to appear on the storefront.
Note: If the correct content is not displaying past the 60 seconds threshold, you must investigate the TTL configuration of your storefront application.
Copilot scenarios
The cache is 1000 seconds when there are new changes to the Storefront’s descriptor files. That is, any changes will take a maximum of 17 minutes to reflect on the Copilot XM application