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.
  • 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 offerings using a configurable hierarchical structure.
  • Universal Descriptors
    • Universal Descriptors are customizable JSON components that you can utilize in your pages and global elements. You can map universal descriptors to UI components in your storefront and manage their look and feel.

Click here to view the Knowledge Base articles on XM

Prerequisites

Copilot is the primary interface for working with fabric applications. To access Copilot, you will 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.

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. This content will be populated on the storefront for display.

Note: XM Components do not return HTML content (except for the RichText types). They return mapped JSON schemas and content so that the storefront can display these components as they see fit.

Universal descriptors are the components you will define for 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). They also describe the data attributes and data types of those attributes.

Creating an XM Component

You can upload a JSON file to the Settings tab on XM with a list of components that you want to define.

Settings page on XM with no Universal Descriptors

Settings page on XM with Universal Descriptors

The following attributes are required:

  • type : The type of descriptor. Set as "Component".
  • id : The ID of the descriptor. This must be unique.
  • label : The human readable label of the descriptor. This label will appear in the editor when you are selecting components to utilize.
  • description : The description that describes the descriptor's intended function.
  • attributes : A custom JSON object that will have each attribute that you want your descriptor's schema to have. Each attribute must have the following information:
    • type : The type of attribute. See (available types) [#availabletypes] for options.
    • label : The human readable label of the attribute
    • default : An optional field, this will auto-populate the editor with a default value if none is specified.

For example:

[
    {
        "type": "Component",
        "id": "ExampleComponent",
        "label": "Example Component",
        "description": "This is an example component",
        "attributes": {
            "title": {
                "type": "String",
                "label": "Title",
                "default": ""
            },
            "description": {
                "type": "MultiLineString",
                "label": "Description",
                "default": "A default description here"
            },
            "color": {
                "type": "String",
                "label": "Color",
                "default": "black"
            },
            "image": {
                "type": "Image",
                "label": "Image",
                "url": {
                    "type": "String",
                    "label": "URL",
                    "default": ""
                },
                "altText": {
                    "type": "String",
                    "label": "Alt Text",
                    "default": ""
                }
            }
        }
    }
]

Note: This is how the ExampleComponent descriptor will appear in the XM editor.

Example component

Defining the component and mapping to a UI component

In your frontend code, create a React component that will map directly to a universal descriptor. It is recommended to name the React component with the same ID as specified in its corresponding Universal Descriptor. 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.

{
    "type": "ExampleNestedComponent",
    "id": "ExampleNestedComponent",
    "label": "Example Nested Component",
    "description": "An example nested component",
    "allowNestedComponents": true,
    "attributes": {
        "someCustomAttribute": {
            "type": "String",
            "label": "Title",
            "default": ""
        },
        "anotherCustomAttribute": {
            "type": "String",
            "label": "Title",
            "default": ""
        }
    }
}

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: [{…}],
}

**Note: XM only supports one level of nested components at this time.

Types

Available Types

The following types are available in XM:

  • 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. For example:

{
    ... other attributes,
    items: {
        "type": "Array",
        "label": "List of slide items",
        "children": {
            "type": "Shape",
            "children": {
                "title": {
                    "type": "String",
                    "label": "Slide title",
                    "default": "",
                },
                ... more array item attributes here
            }
        }
    }
}

No two attributes should have the same name when they are at the same level of the hierarchy.

Image Type x Brandfolder

We have partnered with Brandfolder to provide a comprehensive Digital Asset Management experience in XM.

Begin by uploading assets (including images, videos, gifs, and more) to Brandfolder directly. You may edit asset detail information in Brandfolder.

Upload asset into Brandfolder

View and edit asset details in Brandfolder

View and edit custom fields in Brandfolder

To utilize the assets in your storefront, define a descriptor with an 'Image' type. A customized XM <> Brandfolder modal will appear and allow you to select the asset you defined in Brandfolder and add it to your component.

View images from Brandfolder in XM

View selected asset in XM with details from Brandfolder

Your asset will be saved to your component as a CDN URL to ensure to ensure fast delivery of your content.

Storefronts can optimize image calls to maximize performance leveraging optimization parameters for images - Image Optimizer reference | Fastly Developer Hub

**NOTE: We currently only support images in XM. Video support will be coming shortly.

Enable XM Preview

XM expects the storefront to provide two public routes:

  • /cms/preview
  • /cms/preview-gc

XM requires these routes to preview a page (for /cms/preview) and preview a global component (/cms/preview-gc).

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 file:

import { Preview } from '@teamfabric/xpm'

Naming Components

It is recommended 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 ProductCarousel and that your component has the same name ProductCarousel.

Sample:

// components.js

import ProductCarousel from 'components/ProductCarousel';

export const componentsById = {
    ProductCarousel: ProductCarousel,
};

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. Ultimately, this file is customizable to how you see fit.

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} />
           <Preview componentsById={componentsById} />
           <Footer {...footerProps} />
       </>
   );
};

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: While it is not mandatory that these IDs match, it is highly recommended for ease of organization.

/cms/preview-gc Route

The /cms/preview-gc route renders the global components. This file too is customizable to fit your needs, but an example is provided below:

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.

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

Your 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

All customers can make use of our CDN-enabled endpoints.

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. Ask your Customer Success representative if you need help identifying this.

Note:

  • The header can also be called X-Site-Context for stylistic preference.

API List

Multichannel

You can take advantage of our Multichannel feature to manage more than one channel on our platform. A channel can be a web store, app, etc. For example, if you may have a channel for the DTC arm of your business, and another channel for the B2B segment. Connect with your Customer Success representative to get your account set up to support this feature.

Following the same steps outlined above in the section above to call XM APIs from the storefront, you may specify the channel attribute in the x-site-context header to correspond to the ID of the channel you would like to connect to.

There is no limitation to how you structure the code for each channel, whether you have the same codebase for all of your channels or separate repositories for each. XM only requires each channel's URL to properly integrate with the Multichannel feature.

With Universal Descriptors, the only caveat to consider is the uniqueness of the universal descriptor's schema and its ID. For example, if Channel 1 will have a ProductCarousel component that has title and subtitle attributes, and Channel 2 has a ProductCarousel component that has image and skuId attributes, then this can cause an issue because these schemas are not the same. In this case, it is recommended that you name these components differently, e.g. ProductCarouselWithTitle and ProductCarouselWithImage. However, if the ProductCarousel component will have the same attributes for each channel, then you can use the same universal descriptor across channels with no issue.

All in all, XM has no visibility into the storefront code and is not opinionated on how it's organized, making it a truly headless offering.

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 UI 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 received 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 storefront URL.
    • Ensure that your /cms/preview and /cms/preview-gc routes are defined. Refer to: Enable XM Preview .
    • Ensure that there are no issues arising from your storefront code:
      • Are there any errors being thrown by the storefront?
      • Is the CSS (e.g. media queries) of this page hindering any rendering of a component?

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 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.

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 a custom 404 – Page Not Found page, 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 displays 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.