Skip to main content
Version: 3.x

Storefront Content

X-Ray allows your contributors to have an edit link which wraps the Contentful data, allowing them to directly navigate to the Contentful editor when in contribution mode.

Prerequisites

To use X-Ray, ensure you have followed the Magic Button documentation to setup Contribution Mode.

Adding the @storefrontContent directive

To enable the @storefrontContent directive, you need to add it to your graphql schema, if we take the example from content-types documentation, it would look like this:

type AcmeCmsPage @storefrontContent(extractorIdentifier: "AcmeCmsPage") {
title: String
seo: Seo
}

type AcmeHomePage @storefrontContent(extractorIdentifier: "AcmeHomePage") {
title: String
blocks: [AcmeHomePageBlock]
seo: Seo
}

type AcmeSeo {
title: String
description: String
}

union AcmeHomePageBlock = BlockTitleText | BlockTextImage

type BlockTitleText @storefrontContent(extractorIdentifier: "BlockTitleText") {
title: String
subtitle: String
align: String
}

type BlockTextImage @storefrontContent(extractorIdentifier: "BlockTextImage") {
title: String
picture: String
picturePosition: String
}

Using the StorefrontContentType to define your content types

Previously you had to define your content types using the ContentType class, now you need to use the StorefrontContentType class instead, this class will allow you to add the sys to your contentfulFragment and define the identifierValue to map a unique identifier for this content type.

import { ContentType } from "@front-commerce/contentful";
import { StorefrontContentType } from "@front-commerce/contentful";

class CmsPage extends ContentType {...}
class CmsPage extends StorefrontContentType {...}

Adding the sys field to you query

In order to this will eventually be used to build a url to the contentful editor, the sys fragment is exposed in the StorefrontContentType class and can be called with this.sys.

  get contentfulFragment() {
return gql`
fragment HomepageFragment on Homepage {
pageTitle
pageSlug
${this.sys}
}
`;
}

Defining the identifierValue

The StorefrontContentType adds an additional parameter to the constructor: the identifierValue. This parameter will allow @storefrontContent directive to extract the correct identifier from the content.

IMPORTANT: The identifierValue must be a scalar value or a function returning one. In the above example, the slug field cannot be an array or an object.

Using a function to extract the identifierValue

In the case were you have a content type with multiple entries, you need to define a function to extract the identifierValue, for example the AcmeCmsPage content type can be identified with the unique slug field:

import { StorefrontContentType } from "@front-commerce/contentful";

class CmsPage extends StorefrontContentType {
constructor() {
super(
"cmsPage",
"AcmeCmsPage",
(contentfulData) => ({
title: contentfulData.pageTitle,
slug: contentfulData.pageSlug,
}),
(formattedData) => formattedData.slug // this is the identifierValue which will be used by the @storefrontContent directive
);
}

get contentfulFragment() {
return gql`
fragment HomepageFragment on Homepage {
pageTitle
pageSlug
${this.sys}
}
`;
}
}

Using a scalar value as the identifierValue

In the case where you have a unique content type, you can directly use a scalar value as the identifierValue, for example a Homepage content type:

import { StorefrontContentType } from "@front-commerce/contentful";

class Homepage extends StorefrontContentType {
constructor() {
super(
"homepage",
"AcmeHomepage",
(contentfulData) => ({
title: contentfulData.pageTitle,
}),
"home" // Here we mapped home a scalar value as the identifierValue
);
}

get contentfulFragment() {
return gql`
fragment HomepageFragment on Homepage {
pageTitle
${this.sys}
}
`;
}
}

Register your StorefrontContentType

You need to register your defined StorefrontContentType via the Contentful loader. This is typically done in a loader, or a context enhancer for simpler use cases.

First ensure you have a GraphQLModule definition:

import { createGraphQLModule } from "@front-commerce/core/graphql";

export default createGraphQLModule({
namespace: "Example/Contentful/Homepage",
dependencies: ["Front-Commerce/Contentful/Core"],
loadRuntime: () => import("./runtime"),
typeDefs: /** GraphQL */ `
type AcmeCmsPage @storefrontContent(extractorIdentifier: "AcmeCmsPage") {
title: String
seo: Seo
}

type AcmeHomePage @storefrontContent(extractorIdentifier: "AcmeHomePage") {
title: String
blocks: [AcmeHomePageBlock]
seo: Seo
}

type AcmeSeo {
title: String
description: String
}

union AcmeHomePageBlock = BlockTitleText | BlockTextImage

type BlockTitleText @storefrontContent(extractorIdentifier: "BlockTitleText") {
title: String
subtitle: String
align: String
}

type BlockTextImage @storefrontContent(extractorIdentifier: "BlockTextImage") {
title: String
picture: String
picturePosition: String
}
`,
});

Then you can add your GraphqlRuntime definition to register your StorefrontContentType:

import { createGraphQLRuntime } from "@front-commerce/core/graphql";

export default createGraphQLRuntime({
contextEnhancer: ({ loaders }) => {
const sharedBlocks = {
TitleText: new BlockTitleText(),
TextImage: new BlockTextImage(),
};

const Homepage = new HomepageLoader(sharedBlocks);
loaders.Contentful.registerStorefrontContentType(Homepage);

for (const block of Object.values(sharedBlocks)) {
if (block instanceof StorefrontContentType) {
loaders.Contentful.registerStorefrontContentType(block);
}
}

return {
Homepage,
};
},
});
IMPORTANT

You need to register all the content types which use the @storefrontContent directive, and they should all extend the StorefrontContentType class.

Adding the StorefrontContent component in your application

You need to add the StorefrontContent component in your application, this component will be used to wrap the contentful data and add the edit link.

export default function CmsPage(props) {
const { data } = props; // represents the data from your query;

return (
<StorefrontContent type={data?.__typename} id={props.data?.slug}>
<h1>{data.title}</h1>
</StorefrontContent>
);
}