# Developing integrations for version 1 of the Frontend SDK 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](/frontend-development/api-hub-configuration.md). The [frontend-composable-commerce SDK integration](https://github.com/FrontasticGmbH/frontend-composable-commerce/tree/1.4.3) and [the matching backend extension](https://github.com/FrontasticGmbH/extension-commercetools/tree/1.19.0) are an example of a fully functioning integration. ## 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](/frontend-development/installing-the-frontend-sdk.md#install-the-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`](https://github.com/FrontasticGmbH/frontend-sdk/tree/1.13.2/src/library/Integration.ts) 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](https://github.com/FrontasticGmbH/frontend-composable-commerce/blob/master/src/library/Integration.ts) 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. The `Integration` abstract class also defines the `CustomEvents` generic type to extend the [`StandardEvents`](https://github.com/FrontasticGmbH/frontend-sdk/tree/1.13.2/src/types/events/StandardEvents.ts) type, which is of type [`Events`](https://github.com/FrontasticGmbH/frontend-sdk/tree/1.13.2/src/types/events/Events.ts). 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. ```tsx title="Basic SDK integration implementation" import { SDK, Integration, SDKResponse, ServerOptions, } from '@commercetools/frontend-sdk'; import { Cart } from '@types/cart/Cart'; /** * Define a type for the integration's custom events, this will be exported * from the project index along with the integration. This type is used in * the generic argument of the SDK and Integration. */ type MyCustomEvents = { emptyCartFetched: { cartId: string }; }; /** * Define a type for the payload your action will take, this will be sent * in the body of the request (optional). */ type MyFirstActionPayload = { account: { email: string; }; }; /** * Define a type for the query your action will take, this will be appended to * the URL of the request (optional). */ type MyFirstActionQuery = { name: string; }; /** * Define a type for the action, typing this action takes advantage of the * generic nature of the SDK's callAction method and lets the user know the * return type. */ type MyFirstAction = ( payload: MyFirstActionPayload, query: MyFirstActionQuery, options: { serverOptions?: ServerOptions } = {} ) => Promise>; /** * Define the class and extend the SDK's abstract Integration class, passing * along the MyCustomEvents type */ class MyIntegration extends Integration { /** * Define your action, ensuring explicit types, this will tell the user * the return type and required parameters, using the generic behavior * of the callAction method on the SDK. */ private myFirstAction: MyFirstAction = ( payload: MyFirstActionPayload, query: MyFirstActionQuery, options: { serverOptions?: ServerOptions } = {} ) => { /** * Return the call of the SDK callAction method by passing the * action name (the path to the backend action), the payload, * the query, and serverOptions to support SSR cookie handling. */ return this.sdk.callAction({ actionName: 'example/myAction', payload, query, serverOptions: options.serverOptions, }); }; // Define the type of the example domain object. example: { myFirstAction: MyFirstAction; }; /** * Define the constructor with the SDK singleton as an argument, * passing the MyCustomEvents type again. */ constructor(sdk: SDK) { // Call the super method on the abstract class, passing it the SDK. super(sdk); /** * Initialize any objects with defined methods. This pattern * improves user experience for complex integrations because actions * are called in the format sdk.... */ this.example = { myFirstAction: this.myFirstAction, }; } } // Export the integration to be imported and exported in the package index. export { MyIntegration, MyCustomEvents }; ``` In this code example: 1. The `MyCustomEvents` type is based on the base SDK's [`Events`](https://github.com/FrontasticGmbH/frontend-sdk/tree/1.13.2/src/types/events/Events.ts). In practice, we recommended you to define the type in another file for readability reasons. 2. The `MyFirstActionPayload` type defines the action's optional payload argument. This type defines what must be passed into the integration's action call, which is serialized into the body of the request. 3. The `MyFirstActionQuery` type defines the action's optional query argument. It must be an object of type [`AcceptedQueryTypes`](https://github.com/FrontasticGmbH/frontend-sdk/tree/1.13.2/src/types/Query.ts), which accepts any serializable JSON type. This query is appended to the URL of the action call. Following the example, it would be `/frontastic/example/myAction?name=`. 4. The `MyFirstAction` type defines the type of the action function. The parameters typed with the `MyFirstActionPayload` and `MyFirstActionQuery` arguments, and the return type of `Promise>` are specified. By specifying the return of `SDKResponse` with the `Cart` generic argument, the generic argument of the SDK's `callAction` method is defined and made type-safe. This way, the integration user knows on a successful action call they will have a return type of `{ isError: false, data: Cart }`. 5. The `MyIntegration` class extends the base SDK's [`Integration`](https://github.com/FrontasticGmbH/frontend-sdk/tree/1.13.2/src/library/Integration.ts) abstract class, and passes the `MyCustomEvents` type to the generic argument. This lets you trigger and/or add event handlers for custom events as well as the base SDK's `StandardEvents` commerce types. To interact with another integration's custom events, you must import its events and add the type to the generic argument with an intersection. 6. The `myFirstAction` function is defined with the `MyFirstAction` type. The function is marked as private so it cannot be called directly on the class. However, in practice, this will likely be defined elsewhere and set up externally. For more information about how these actions can be set up, see [frontend-composable-commerce Integration](https://github.com/FrontasticGmbH/frontend-composable-commerce/blob/master/src/library/Integration.ts) constructor. On the return of this method, the SDK's `callAction` method is returned, passing the `actionName`, `payload`, and `query`. The `actionName` will match the backend's default export action structure, for `actionName: 'example/myAction'` the default export will call the following action on the backend. ```ts title="example/myAction actionName backend association" export default { 'dynamic-page-handler': { ... }, 'data-sources': { ... }, actions: { example: { myAction: } } } ``` 7. The type of the `example` object is defined, where the methods for the actions in the `example` domain are also defined. Structuring the methods in this way creates a tree structure for callable methods. For example, a typical commerce integration might have `account`, `cart`, `product`, and `wishlist` domains for actions. Therefore, by structuring the methods in this way, it is easier to find the methods to be called. An example of the domain can be found in `@commercetools/frontend-composable-commerce` in the [`CartActions`](https://github.com/FrontasticGmbH/frontend-composable-commerce/blob/master/src/library/actions/CartActions.ts). 8. The `constructor` is defined with the `sdk` singleton passing the `MyCustomEvents` as a generic argument. This lets you trigger and/or add event handlers for custom events as well as the base SDK's `StandardEvents` commerce types. To interact with another integration's custom events, you must import its events and add the type to the generic argument with an intersection. Then, the `super` keyword is used to invoke the constructor of the abstract base class passing it the `sdk`. Finally, the example domain object is set up with the `this.myFirstAction` method definition. 9. The `MyIntegration` and `MyCustomEvents` types are exported and imported to the package's index file. From there, they are exported to be imported into your commercetools Frontend project. In this example, the return type is `Promise>`. The response from the SDK can be of following types: - [`SDKResponse`](https://github.com/FrontasticGmbH/frontend-sdk/tree/1.13.2/src/types/SDKResponse.ts) type from the base SDK. - `{ isError: false, data: T }` on success, where the `T` type is generic and defines the type of data fetched on success, which you can specify by passing the type to the SDK’s `callAction` method (`sdk.callAction`) as the generic argument. - `{ isError: true, error: FetchError }` on error. For further information about structuring a large-scale integration, see [`frontend-composable-commerce` integration](https://github.com/FrontasticGmbH/frontend-composable-commerce). ## 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` must be fully typed for the SDK to accept the handler argument along with `"emptyCartFetched"` as the value of the `eventName` parameter. ```tsx title="Add and remove event handlers" 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](/frontend-development/frontend-sdk.md#sdk-template-file) 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. ```tsx title="Call the trigger method" getCart: async () => { const response = await sdk.callAction({ 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](https://www.typescriptlang.org/docs/handbook/2/narrowing.html) to work on the [SDKResponse](https://github.com/FrontasticGmbH/frontend-sdk/tree/1.13.2/src/types/SDKResponse.ts) 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. ## Related pages - [Area overview page with navigation](/frontend-development.md) - [Previous page: Using the Frontend SDK](/frontend-development/using-the-frontend-sdk-v1.md)