> This page is for Developer.

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.bigcommerce.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.bigcommerce.com/_mcp/server.

# Lab - Enable Makeswift for Product FAQs

Plan: Composable Developer

Lesson 21 of 22 · 45 min

### In this lab, you will:

* Add Makeswift editing capabilities to existing Product FAQs functionality on the Catalyst product detail page
* Make use of *MakeswiftComponent*

### Prerequisites

* The [starter codebase](https://github.com/bigcommerce-edu/lab-catalyst-makeswift-faqs) installed and connected with your Makeswift site, as done in the previous labs

## Setup

This exercise continues where you left off previously in your Makeswift project. Start the dev server from the project root if it's not currently running:

```text showLineNumbers={false}
pnpm run dev
```

If you need a fresh start, you can follow the instructions below to set up a new project complete with previous exercise code.

1. **Install** *degit*.

```text showLineNumbers={false}
npm install -g degit
```

2. In your terminal, **run** the following, replacing the path to the working directory with your own path:

```text showLineNumbers={false}
degit https://github.com/bigcommerce-edu/lab-catalyst-makeswift-faqs#e-makeswift-faqs-start /path/to/working/directory
```

3. **Navigate** to the new project working directory and **initialize** a Git repository for your project.

```text showLineNumbers={false}
cd /path/to/working/directory
git init
git add .
git commit -m "Initial project files"
```

4. **Install** the project dependencies.

```text showLineNumbers={false}
corepack enable pnpm && pnpm install
```

5. **Copy** *.env* as *.env.local* and **modify** *.env.local* with configuration details for your store.
6. **Start** the dev server process:

```text showLineNumbers={false}
cd /path/to/working/directory
pnpm run dev
```

### Exercise Reference Copy

If it's helpful to compare your own code as you go, you can clone or download the completed version of this lab as a reference.

[Product FAQs Component Lab Snapshot](https://github.com/bigcommerce-edu/lab-catalyst-makeswift-faqs/tree/e-makeswift-faqs-complete)

## The Product FAQs Component

Your starter codebase already includes a custom feature that displays frequently asked questions on the product detail page.

![Product FAQs](/_fern-files/bigcommerce.docs.buildwithfern.com/fc443531a246ac29263a70dac8c04d8dce256c49125431c2e58257eb675e5481/assets/learn/makeswift-core/product-faqs.png)

This existing component implementation fetches product metafield data to provide the FAQs that are rendered.

> **Info**
>
> This feature in its starting state is the result of custom labs from the Catalyst Core course. For instructions on preparing your product data with the appropriate metafields, see that course.

In this exercise, you'll enhance the existing capabilities by providing Makeswift properties allowing users to create FAQs directly in the editor, replacing or adding to the metafield-based FAQs.

## Step 1: Embed the Component with Makeswift

Your first step is to modify the way the basic *ProductFaqs* client component is rendered in the page, "wrapping" it in a component that will ultimately provide Makeswift-controlled props.

1. **Open** the file *components/custom/product-faqs/makeswift/client.tsx* and **add** an appropriate React context provider to supply the FAQs loaded from product metafields.

```jsx {5-10,15} showLineNumbers={false}
...

export type ContextProps = ComponentPropsWithoutRef<typeof ProductFaqs>;

const ProductFaqsContext = createContext<ContextProps>({
  productId: 0,
  heading: '',
  limit: 10,
  faqsCollection: { endCursor: null, faqs: [] },
});

export const ProductFaqsContextProvider = (
  ...
) => (
  <ProductFaqsContext.Provider value={value}>{children}</ProductFaqsContext.Provider>
);

...
```

*ContextProps* is based on the props expected by the *ProductFaqs* component. Here you are establishing default values for two of those props (\_limit\_and *faqs*), while the rest can also be supplied with the context provider.

2. **Modify** *MakeswiftProductFaqs* to fetch values from the context provider and pass them into the props of *ProductFaqs*.

```jsx {5,7-25} showLineNumbers={false}
export const MakeswiftProductFaqs = forwardRef(
  (
    ...
  ) => {
    const { productId, limit, faqsCollection: streamableFaqsCollection, heading } = useContext(ProductFaqsContext);

    return (
      <Stream fallback={<ProductFaqsSkeleton />} value={streamableFaqsCollection}>
      {(passedFaqsCollection) => {

        return (
          <div ref={ref}>
            <ProductFaqs
              faqsCollection={passedFaqsCollection}
              heading={heading}
              limit={limit}
              productId={productId}
              showLoadMore={true}
            />
          </div>
        );

      }}
      </Stream>
    );
  }
);
```

**Note** the use of the `<Stream>` component wrapping `<ProductFaqs>`. This pattern is covered in more detail in the labs focused on building the initial Product FAQs UI.  `<Stream>` allows rendering a fallback until the "streamable" value is eventually resolved. The same pattern is used within `ProductFaqs` itself.

Here, we're waiting for the FAQs collection to be available before rendering `ProductFaqs` because we will eventually need to manipulate the list before passing it on.  This won't require changing anything in the original component, because the streamable pattern is flexible enough to handle values that are still resolving *or* have already resolved.

3. **Open** the file *components/custom/product-faqs/makeswift/register.tsx* and **add** the *registerComponent* call.

```jsx {5-12} showLineNumbers={false}
...

export const COMPONENT_TYPE = 'catalyst-makeswift-product-faqs';

runtime.registerComponent(MakeswiftProductFaqs, {
  type: COMPONENT_TYPE,
  label: 'MakeswiftProductFaqs (private)',
  hidden: true,
  props: {

  },
});
```

4. **Open** the file *lib/makeswift/components.ts* and **add** the import statement for the component.

```js showLineNumbers={false}
import '~/components/custom/product-faqs/makeswift/register';
```

With the actual Makeswift component now registered, our next step is to build the final wrapper component that will accept the same props as `ProductFaqs`, set those in the context provider, and render `MakeswiftProductFaqs` as a built-in element.

5. **Open** the file *components/custom/product-faqs/makeswift/index.tsx* and **modify** the component to accept the appropriate props, load a component snapshot, and embed `MakeswiftComponent`.

```jsx {2-5,7,9-22} showLineNumbers={false}
export async function ProductFaqs({
  productId,
  limit,
  faqsCollection,
  heading,
}: ContextProps) {
  const snapshot = await getComponentSnapshot(`product-faqs-${productId}`);

  return (
    <ProductFaqsContextProvider value={{
      productId,
      limit,
      faqsCollection,
      heading,
    }}>
      <MakeswiftComponent
        label="Product FAQs"
        snapshot={snapshot}
        type={COMPONENT_TYPE}
      />
    </ProductFaqsContextProvider>
  );
}
```

Notice that the ID supplied to *getComponentSnapshot* is unique to the product ID that was passed in the component's props, meaning that Makeswift content and configuration for this component will be on a per-product basis.

This component is now ready to be swapped into the context where `ProductFaqs` was previously rendered directly.

6. **Open** the file *app/\[locale]/(default)/product/\[slug]/page.tsx* and replace the old `ProductFaqs` import with the Makeswift version.

```js showLineNumbers={false}
import { ProductFaqs } from '~/components/custom/product-faqs/makeswift';
```

7. **Browse** to a detail page for a product containing FAQ metafields and **verify** that the typical FAQ presentation still functions.
8. **Open** the Makeswift editor with the appropriate dev site selected, **enter** the URL path of the product detail page in the location bar, and **verify** that the FAQs section is a selectable component.

![Selecting the FAQ component in the editor](/_fern-img/db554097808f0e741a9af9c4ab71fa09ef522e9bef2ad47ce01fb98ae610b256.webp)

Your component doesn't yet define any editable properties, but you've successfully registered it with Makeswift while preserving its original functionality.

[Example Code](https://github.com/bigcommerce-edu/lab-catalyst-makeswift-faqs/compare/e-makeswift-faqs-01-pre...e-makeswift-faqs-01-post?diff=split)

## Step 2: Add FAQs Prop Control

You'll now add a Makeswift control for creating additional FAQs directly in the editor.

1. **Open** the file *components/custom/product-faqs/makeswift/register.tsx* and **add** the *faqs* property.

```js {4-14} showLineNumbers={false}
runtime.registerComponent(MakeswiftProductFaqs, {
  ...
  props: {
    faqs: List({
      label: 'FAQs',
      type: Group({
        label: 'FAQ Details',
        props: {
          question: TextInput({ label: 'Question', defaultValue: 'Question' }),
          answer: TextInput({ label: 'Answer', defaultValue: 'Answer' }),
        },
      }),
      getItemLabel: (section) => section?.question || 'Question',
    }),
  }
});
```

For now, the List control only supports simple text inputs for both question and answer.

2. **Open** the file *components/custom/product-faqs/makeswift/client.tsx* and **add** the appropriate props interface definition.

```js {1-4,7,13} showLineNumbers={false}
interface ProductFaq {
  question: string;
  answer: string;
}

interface ProductFaqsProps {
  faqs: ProductFaq[];
}

export const MakeswiftProductFaqs = forwardRef(
  (
    {
     faqs,
    }: ProductFaqsProps,
    ref: Ref<HTMLDivElement>,
  ) => {
   ...
 }
);
```

3. **Modify** *MakeswiftProductFaqs* to combine the metafield-provided FAQs from the context provider with those supplied by the Makeswift prop.

```jsx {7-15,21-28,33} showLineNumbers={false}
export const MakeswiftProductFaqs = forwardRef(
  (
    ...
  ) => {
    const { productId, limit, faqsCollection: streamableFaqsCollection, heading } = useContext(ProductFaqsContext);

    const formattedFaqs = faqs.map(
      (faq, index) => {
        return {
          key: index.toString(),
          question: faq.question,
          answer: faq.answer,
        };
      }
    );

    return (
      <Stream ...>
      {(passedFaqsCollection) => {

        const allFaqs = formattedFaqs.concat(
          passedFaqsCollection.faqs
        );

        const allFaqsCollection = {
          endCursor: passedFaqsCollection.endCursor,
          faqs: allFaqs,
        };

        return (
          <div ref={ref}>
            <ProductFaqs
              faqsCollection={allFaqsCollection}
              ...
            />
          </div>
        );

      }}
      </Stream>
    );
  },
);
```

4. **Open** the product detail page in the Makeswift editor and **verify** that additional FAQs can be added.

[Example Code](https://github.com/bigcommerce-edu/lab-catalyst-makeswift-faqs/compare/e-makeswift-faqs-02-pre...e-makeswift-faqs-02-post?diff=split)

## Step 3: Add a Slot Option

So far, each FAQ added via Makeswift can contain only simple text for the "answer" portion. You can offer even more flexibility by providing the option to build an answer entirely with the editor canvas. You'll add a new Slot control for this, along with a property to select which style of content a FAQ will use.

1. **Open** the file *components/custom/product-faqs/makeswift/register.tsx* and **add** two new properties to the Group definition for each FAQ.

```js {10-11} showLineNumbers={false}
runtime.registerComponent(MakeswiftProductFaqs, {
  ...
  props: {
    faqs: List({
      label: 'FAQs',
      type: Group({
        label: 'FAQ Details',
        props: {
          ...
          richContent: Checkbox({ label: 'Use rich content slot', defaultValue: false }),
          content: Slot(),
        },
      }),
      ...
    }),
  },
});
```

2. **Open** the file *components/custom/product-faqs/makeswift/client.tsx* and **add** the new props to the interface.

```js {4-5} showLineNumbers={false}
interface ProductFaq {
  question: string;
  answer: string;
  richContent: boolean;
  content: ReactNode;
}
```

3. **Modify** the *map* callback that compiles the *formattedFaqs* array to utilize the new props.

```jsx {11} showLineNumbers={false}
export const MakeswiftProductFaqs = forwardRef(
  (
    ...
  ) => {
    ...

    const formattedFaqs = faqs.map(
      (faq, index) => {
        return {
          ...
          answer: (faq.richContent) ? faq.content : faq.answer,
        };
      }
    );

    return (
      ...
    );
  },
);
```

The final list of FAQs being passed in the `faqsCollection` prop can now include either a string *or* a `ReactNode` for each `answer`.  We need to update the props schema in `FaqsList` accordingly.

4. **Open** the file *components/custom/product-faqs/faqs-list.tsx* and modify the `Faq` interface.

```js {4} showLineNumbers={false}
export interface Faq {
  key: string;
  question: string;
  answer: string | ReactNode;
}
```

5. **Open** the product detail page in the Makeswift editor and **verify** that you can use the "Use rich content slot" toggle to enable an editable region for an FAQ.

![FAQs extra slots](/_fern-files/bigcommerce.docs.buildwithfern.com/699116d334092aaec7175ef0f7f32513b4d90c9658ac3b51841f5f4a71dd07a3/assets/learn/makeswift-core/faqs-slot.gif)

[Example Code](https://github.com/bigcommerce-edu/lab-catalyst-makeswift-faqs/compare/e-makeswift-faqs-03-pre...e-makeswift-faqs-03-post?diff=split)

## Step 4: Make Metafield FAQs Optional

As a final enhancement, you'll introduce a Makeswift control that will dictate whether FAQs based on metafields will be shown at all. This will allow editors to decide, on a product-by-product basis, whether manually created FAQs will replace or add to those sourced from the product data.

1. **Open** the file *components/custom/product-faqs/makeswift/register.tsx* and **add** the *showOriginal* prop control.

```js {7} showLineNumbers={false}
runtime.registerComponent(MakeswiftProductFaqs, {
  ...
  props: {
    faqs: List({
      ...
    }),
    showOriginal: Checkbox({ label: 'Show metafields-based FAQs', defaultValue: true }),
  },
});
```

2. **Open** the file *components/custom/product-faqs/makeswift/client.tsx* and **add** the new prop.

```js {5,12} showLineNumbers={false}
...

interface ProductFaqsProps {
  faqs: ProductFaq[];
  showOriginal: boolean;
}

export const MakeswiftProductFaqs = forwardRef(
  (
    {
      faqs,
      showOriginal,
    }: ProductFaqsProps,
    ref: Ref<HTMLDivElement>,
  ) => {
    ...
  }
);
```

3. **Modify** the *concat* expression that combines all FAQs to make the list from the context provider conditional.

```js {12,16} showLineNumbers={false}
export const MakeswiftProductFaqs = forwardRef(
  (
    ...
  ) => {
    ...

    return (
      <Stream ...>
      {(passedFaqsCollection) => {

        const allFaqs = formattedFaqs.concat(
          showOriginal ? passedFaqsCollection.faqs : []
        );

        const allFaqsCollection = {
          endCursor: showOriginal ? passedFaqsCollection.endCursor : null,
          faqs: allFaqs,
        };

        return (
          ...
        );

      }}
      </Stream>
    );
  },
);
```

4. **Modify** the returned JSX to also make the `showLoadMore` prop dependent on the same value.

```jsx {17} showLineNumbers={false}
export const MakeswiftProductFaqs = forwardRef(
  (
    ...
  ) => {
    ...

    return (
      <Stream ...>
      {(passedFaqsCollection) => {

        ...

        return (
          <div ref={ref}>
            <ProductFaqs
              ...
              showLoadMore={showOriginal}
            />
          </div>
        );

      }}
      </Stream>
    );
  },
);
```

5. **Open** the product detail page in the Makeswift editor and **verify** that toggling the "Show metafields-based FAQs" control will show or hide the appropriate FAQs.

![FAQs toggle control](/_fern-files/bigcommerce.docs.buildwithfern.com/b6f65801d6a62c6d3d00244826f0bbb22e6411134a919b937b6a33152c4dd09c/assets/learn/makeswift-core/faq-toggle.gif)

[Example Code](https://github.com/bigcommerce-edu/lab-catalyst-makeswift-faqs/compare/e-makeswift-faqs-04-pre...e-makeswift-faqs-04-post?diff=split)

## Full Lab Code

[Full Lab Code](https://github.com/bigcommerce-edu/lab-catalyst-makeswift-faqs/compare/e-makeswift-faqs-start...e-makeswift-faqs-complete?diff=split)