Developing your extensions

Within commercetools Frontend, extensions are of two kinds:

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

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

This documentation covers the development of frontend SDK extension. For information on developing backend extensions, see Developing extensions.

The @commercetools/frontend-composable-commerce SDK extension and the matching backend API are an example of a functioning extension development.

Create your SDK extension project

Create a project for your SDK in your commercetools Frontend project, then run the following commands:

  • yarn add @commercetools/frontend-sdk -D
  • yarn add @commercetools/frontend-sdk -P
  • yarn add @commercetools/frontend-domain-types

The SDK package must be installed as both a peerDependency and a devDependency. The peerDependency is required to ensure that no version mismatch issues occur during release, causing problems with the singleton behavior of the SDK. The devDependency is required for local development.

You can use any build tool to build and bundle your package. The following tsconfig.json settings are recommended.

Recommended tsconfig.json settingsjson
"compilerOptions": {
"target": "ES2022",
"strict": true,
"keyofStringsOnly": true,
"allowSyntheticDefaultImports": true,
"types": ["node"],
"declaration": true,
"outDir": "lib",
"moduleResolution": "node"
"exclude": ["node_modules", "lib"]

It is also recommended that your extension is bundled into one main lib/index.js file, which will be specified in your package.json, and that types are generated following the structure of your project. This can be achieved with some build tools or independently with tsc --emitDeclarationOnly --outDir lib. You can also add the --watch flag for development.

Define methods and types on your extension

The key export of an extension is a class that extends the Extension abstract class exported from @commercetools/frontend-sdk. Your extension must take the SDK singleton in the constructor and store the instance as a property.

The interface also defines the unregisterExtension abstract method which cleans up any event handlers of your extension. This will likely only be used for short-lived extensions so it can be defined as unregisterExtension(): void { }.

An example of the above is the @commercetools/frontend-composable-commerce Extension class. It is recommended to follow its method structure for consistency and ease of use, especially for extensions with many methods and domains.

The Extension abstract class also defines the CustomEvents generic type to extend the @commercetools/frontend-sdk StandardEvents type, which extends the Events type.

Even if you do not need to define any custom events for your extension, it is still recommended to create an empty type to export from your project index. This way, everything will be set to define custom events in the future. If you don't create an empty type, compilation errors will occur. Failure to export this type will cause errors to the user of the extension when trying to add event handlers for these events.

Example of extension implementation

The following is an example of extension implementation. In the sample action, the return type is Cart from the @commercetools/frontend-domain-types library. Commerce types will be mapped to these domain types on the backend, from whichever provider the data is fetched.

The response from the SDK will always be { isError: false, data: T } or { isError: true, error: FetchError }. The FetchError type is defined in the SDK, and the generic type is defined by the type passed to the SDK’s callAction method (sdk.callAction<T>). In the example, the return type is defined as Promise<SDKResponse<Cart>>.

Basic extension implementationTypeScript React
import { SDK, Extension, SDKResponse } from '@commercetools/frontend-sdk';
import { Cart } from '@commercetools/frontend-domain-types/cart/Cart';
* Define a type for the extension's custom events, this will be exported
* from the project index along with the extension. This type is used in
* the generic argument of the SDK and Extension.
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.
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.
type MyFirstActionQuery = {
name: sring;
* 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
) => Promise<SDKResponse<Cart>>;
* Define the class and extend the SDK's abstract Extension class, passing
* along the MyCustomEvents type
class MyExtension extends Extension<MyCustomEvents> {
* 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
) => {
* Return the call of the SDK callAction method by passing the
* action name (the path to the backend action), the payload
* (can be an empty object if not needed), and the query (optional).
return this.sdk.callAction({
actionName: 'example/myAction',
// 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<MyCustomEvents>) {
// Call the super method on the abstract class, passing it the SDK.
* Initialize any objects with defined methods. This pattern
* improves user experience for complex extensions because actions
* are called in the format extension.<domain>.<name>.
this.example = {
myFirstAction: this.myFirstAction,
// Define the unregisterExtension abstract method from the abstract class.
unregisterExtension(): void {}
// Export the extension to be imported and exported in the package index.
export { MyExtension, MyCustomEvents };

The extension implementation example explained

Following is a description of the previous example.

  1. The MyCustomEvents type is defined following the definition of the Events type in the @commercetools/frontend-sdk package. In practice, it is recommended to define the type in another file for readability reasons, as the definition can be large.

  2. The MyFirstActionPayload type is defined for the action's optional payload argument. This type defines what must be passed into the extension's action call, which is serialized into the body of the request. In practice, it is recommended to define the type in another file for readability reasons, as there may be many payload types.

  3. The MyFirstActionQuery type is an optional query type. It must be an object type that follows the pattern [key: string]: string | number | boolean. This query is appended to the URL of the action call. Following the example, it would be <endpoint>/frontastic/example/myAction?name=<nameValue>. In practice, it is recommended to define the type in another file for readability reasons.

  4. The MyFirstAction type defines the type of the function/action itself. The parameters typed with the MyFirstActionPayload and MyFirstActionQuery arguments, and the return type of Promise<SDKResponse<Cart>> are specified. By specifying the return of SDKResponse with the Cart generic argument, the generic argument of the SDK's callAction method is used. This way, the extension user knows on successful action call they will have a return type of { isError: false, data: Cart }. In practice, it is recommended to define the type in another file for readability reasons.

  5. The MyExtension class extends the @commercetools/frontend-sdk Extension abstract class, and passes the MyCustomEvents type to the generic argument. This will allow extension users to trigger and/or add event handlers for custom events as well as the @commercetools/frontend-sdk StandardEvents commerce types. To interact with another extension'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 enforcing the MyFirstAction type. The function is marked private so it cannot be called directly on the class. However, in practice, this will likely be defined elsewhere and set up externally. See the @commercetools/frontend-composable-commerce Extension constructor for an example of how these actions can be set up.
    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.

    example/myAction actionName backend associationTypeScript
    export default {
    'dynamic-page-handler': { ... },
    'data-sources': { ... },
    actions: {
    example: {
    myAction: <function to be called>
  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 e-commerce extension 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 @commercetols/frontend-composable-commerce in the CartActions.

  8. The constructor is defined with the sdk singleton passing the MyCustomEvents as a generic argument. This allows triggering and/or adding event handlers for custom events as well as the @commercetools/frontend-sdk StandardEvents commerce types. To interact with another extension'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 unregisterExtension abstract method is defined. Since the extension is expected to persist for the entire duration of the website's use, it is declared with a void return.

  10. The MyExtension 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.

For further information on structuring a large-scale extension, see the source code of the @commercetools/frontend-composable-commerce extension.

Use the event engine

The commercetools Frontend SDK comes with event management tools. This allows extensions to communicate with other extensions and the user of the extension to add or create an event handler. The source for this functionality is the EventManager class, extended by the SDK. It is also possible to extend the event types with custom events with the generic argument passed from the SDK.

Following is a description of the three methods available on the SDK to manage event handlers:

  • trigger is called to trigger an event, for which an instance of the Event class from @comercetools/frontend-sdk is passed.
    An event is constructed with eventName and data. The eventName corresponds to the [key: string] value in @comercetools/frontend-sdk's StandardEvents. In custom events data corresponds to the type of value set for the event.
    For example, to trigger the emptyCartFetched custom event defined above, trigger is called on the sdk and an event constructed with eventName: 'emptyCartFetched' and data: { cartId: "<cartId>" } is passed.
  • on is called to add an event handler for an event. The method takes the eventName and handler arguments.
    For example, for the emptyCartFetched custom event defined above, emptyCartFetched is passed for the eventName argument and a function with event parameter of type { data: { cartId: "<cartId>" } } is passed for the handler argument.
  • off is called to remove an event handler. For example, to persist the handler only for the lifecycle of a particular component.
    The function takes the same arguments as the on function. For it to work, a named function for the handler argument must be defined. To successfully pass a named function to the handler parameter, the event type in the function's argument must be fully typed, as shown in the following examples.

Events are likely to be triggered only during action calls. Events can be chained but it is recommended to avoid it as this can cause an infinite recursion if handled incorrectly, or if another extension interacting with yours does something similar.

Example of event triggering

Following 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 methodTypeScript React
getCart: async () => {
const response = await sdk.callAction<Cart>({
actionName: 'cart/getCart',
if (!response.isError) {
new Event({
eventName: 'cartFetched',
data: {
if (! || === 0) {
new Event({
eventName: 'emptyCartFetched',
data: {
return response;

Example of event handler addition and removal

Following 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 eventHander 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 handlersTypeScript React
const emptyCartFetched = (
event: Event<
cartId: string;
) => {
// Access in the body of the event handler.
useEffect(() => {
sdk.on('emptyCartFetched', emptyCartFetched);
return () => {'emptyCartFetched', emptyCartFetched);
}, []);

This example assumes an event handler with a lifespan of a single React component. For this reason, the off method is called on the sdk to clean up on unmount. Otherwise, the same handler would be added every time the component is mounted.
In practice, event handlers are likely to persist for the lifespan of the website use. In this case, extension users will call the on method in the SDK constructor template. To add event handlers on your extension itself, call the on method in your extension constructor and pass an anonymous function.

Composable Commerce
Getting StartedMerchant CenterTutorialsHTTP APIGraphQL APIImport & ExportSDKs & Client LibrariesCustom Applications
Getting StartedStudioDevelopingHTTP API
Sign upLog inSupportStatusOfferingTech BlogIntegrationsUser Research Program
Copyright © 2023 commercetools
Privacy PolicyImprint