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

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

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.

  1. 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.
  2. Create a folder in your repository to house the component and its relevant files (tests, styles, etc).
  3. In this folder, create a file named descriptor.js or descriptor.ts .
  4. 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.

Example component

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:

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",
  ...
}

Click here for an example

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 or live )
  • account : The account ID.

Finding your account ID

  1. Log in to Copilot
  2. Navigate to XM
  3. Open the Inspector
  4. Navigate to Application
  5. 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

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 (no null or undefined 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 (no null or undefined objects).

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 the features 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 .
    • 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.

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 404Not 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