Developing integrations for version 1 of the Frontend SDK

Ask about this Page
Copy for LLM
View as Markdown

Custom integrations with commercetools Frontend include both backend and frontend development:

  • Backend extension API: consists of a default export with the required actions, data-sources, and dynamic-page-handler methods merged with the other required integrations.
  • Frontend SDK integration: consumes and utilizes the @commercetools/frontend-sdk package to call backend actions and handle events.

Actions must be developed on the backend extension so that frontend SDK integrations can call them. For ease of development and debugging, it is recommended to develop the backend extension and frontend integration at the same time.

This documentation covers the development of a frontend SDK integration. For information about developing backend extensions, see Developing extensions.

Create the SDK integration

Create a folder for your SDK integration in your commercetools Frontend project inside packages/PROJECT_NAME/frontend/src/sdk. If this folder does not exist, your project might predate the addition of the commercetools Frontend SDK. In such cases, please refer to the installation and setup instructions for the coFE SDK.
When developing an SDK integration, we recommend you keep the base SDK (@commercetools/frontend-sdk) package dependency up to date with the latest release. This package is backward compatible and updating it ensures you have access to the latest features and enhancements. After you complete the integration, you do not need to continuously update the dependency, however, we recommend you periodically check for new features, improvements, and bug fixes.

Implement the SDK integration

The main export of an integration is a class that extends the Integration abstract class, which is imported from @commercetools/frontend-sdk.
The SDK integration must take the SDK singleton in the constructor and store the instance as a property. For example, the frontend-composable-commerce Integration class follows the same pattern. Following this structure provides consistency and ease of use, especially for SDK integrations with many methods and action type domains.

Even if you don't need to define any custom events for an SDK integration, we recommend you create and export an empty type from your integration to let you define custom events in the future. If you don't create an empty type, compilation errors will occur, and failure to export this type will cause errors when adding event handlers for these events.

In the following code example, we implement an SDK integration. In the sample action, the return type is Cart from the coFE domain types at packages/PROJECT_NAME/types. For complete type-safety, the commerce types should be mapped to the domain types on the backend.

SDK integration example

Basic SDK integration implementationtsx
import {
  SDK,
  Integration,
  SDKResponse,
  ServerOptions,
} from '@commercetools/frontend-sdk';
import { Cart } from '@types/cart/Cart';

type MyCustomEvents = {
  emptyCartFetched: { cartId: string };
};

type MyFirstActionPayload = {
  account: {
    email: string;
  };
};

type MyFirstActionQuery = {
  name: string;
};

type MyFirstAction = (
  payload: MyFirstActionPayload,
  query: MyFirstActionQuery,
  options: { serverOptions?: ServerOptions } = {}
) => Promise<SDKResponse<Cart>>;

class MyIntegration extends Integration<MyCustomEvents> {
  private myFirstAction: MyFirstAction = (
    payload: MyFirstActionPayload,
    query: MyFirstActionQuery,
    options: { serverOptions?: ServerOptions } = {}
  ) => {
    return this.sdk.callAction({
      actionName: 'example/myAction',
      payload,
      query,
      serverOptions: options.serverOptions,
    });
  };

  example: {
    myFirstAction: MyFirstAction;
  };

  constructor(sdk: SDK<MyCustomEvents>) {
    super(sdk);

    this.example = {
      myFirstAction: this.myFirstAction,
    };
  }
}

export { MyIntegration, MyCustomEvents };

How the SDK integration example works

In this code example:

SDK response types

In this example, the return type is Promise<SDKResponse<Cart>>. The response from the SDK can be of following types:

Implement event handling

The commercetools Frontend SDK comes with the event engine that lets integrations add event handlers and communicate with other integrations by triggering events.

Add and remove event handlers

The following code is an example of adding and removing an event handler by calling the on and off methods.
First, the emptyCartFetched event handler callback is defined.
Then in the useEffect React lifecycle hook, the on method is called on component mounting.
Finally, a function calling the off method on component unmounting is returned to clean up.
The emptyCartFetched named function is defined to serve as the eventHandler parameter. The event argument type of Event<EventName, EventData> must be fully typed for the SDK to accept the handler argument along with "emptyCartFetched" as the value of the eventName parameter.
Add and remove event handlerstsx
const emptyCartFetched = (
  event: Event<
    'emptyCartFetched',
    {
      cartId: string;
    }
  >
) => {
  // Access event.data.cartId in the body of the event handler.
};

useEffect(() => {
  sdk.on('emptyCartFetched', emptyCartFetched);
  return () => {
    sdk.off('emptyCartFetched', emptyCartFetched);
  };
}, []);
This example sets up an event handler with a lifecycle scoped to a single React component. To avoid duplicate handlers when the component remounts, the off method is called within the cleanup function of useEffect. Without this cleanup, the handler would be re-added each time the component mounts, potentially causing memory leaks or unintended behavior.
For event handlers that should persist beyond a single component's lifecycle, such as those tied to the lifespan of the application, integration users can call the on method within the SDK template constructor. To attach persistent handlers directly within the SDK integration, call the on method in the SDK integration’s constructor and pass an anonymous function.

Trigger custom events

The following code is an example of triggering an event by calling the trigger method.
First, the getCart action is defined, for which a response is returned by the sdk.
Then, the isError parameter is checked to see if the action has errored and the trigger method is called to trigger the standard cartFetched event.
Finally, if the cart is empty, the trigger method is called to trigger the emptyCartFetched event.
Call the trigger methodtsx
getCart: async () => {
  const response = await sdk.callAction<Cart>({
    actionName: 'cart/getCart',
  });

  if (response.isError === false) {
    sdk.trigger(
      new Event({
        eventName: 'cartFetched',
        data: {
          cart: response.data,
        },
      })
    );

    if (!response.data.lineItems || response.data.lineItems.length === 0) {
      sdk.trigger(
        new Event({
          eventName: 'emptyCartFetched',
          data: {
            cartId: response.data.cartId,
          },
        })
      );
    }
  }
  return response;
};
The response.isError must be explicitly compared to the boolean value in non-strict projects for the narrowing to work on the SDKResponse union type. Otherwise, the error Property 'data' does not exist on type will occur on when accessing response.data. For strict projects, a simple truthy/falsy comparison such as !response.isError is sufficient.