Migration guide

As part of our initiatives to better our app development environment and enable developers to build secure apps, the app development platform and the FDK are constantly upgraded. Though most of the upgrades are seamless and inherently handled by the FDK, at times upgrades entail migrating an app manually. In such cases, the FDK throws warning or error messages with appropriate links to resources that help in migration.

If you have an existing app built on platform version 2.3, this document outlines the process you need to follow to migrate your app to the latest platform version, 3.0.

Note:If your app is built on a platform version prior to 2.3, ensure to refer to our Migration guide for platform version 2.3 for guidance on migrating your app to version 2.3 before proceeding with the migration to version 3.0.

Important:When you run Freshworks CLI commands, an upgrade check is performed only if it hasn’t been performed in the last 24 hours. If a check has run in the last 24 hours, migration or upgrade warnings are not displayed.

The migration map

FDK and compatible Node.js versions

FDK versionNode.js versionSupport for Node.js - status
9.0.0 or laterNode 18.xLive
8.0.0 - 8.6.7Node 14.xDeprecated as of December, 2023.
7.0.0 - 7.5.1Node 12.xDeprecated as on September 30, 2022.
Prior to 7.0.0Node 10.xDeprecated.

Migrate to the latest platform version

Construct configuration environment (.fdk/configs.json)

To migrate an app built on prior platform versions to a global app, you should create a configs.json file in the app directory with the global_apps attribute and set the value of global_apps.enabled configuration parameter as true. To do this, run the following command.

fdk config set --scope local global_apps.enabled true

Important: As the configuration is scoped to local, the FDK treats only the app in the current app directory as a global app. As a result, during app building, all global app features and validations are applicable only to the app in the current app directory. For more information, see CLI - Command reference.

To ensure that all global app features and validations are applicable to all apps that the developers may switch to, run the following command.

fdk config set --scope global global_apps.enabled true

Modularise the app use-case

Modularise the app use-case by identifying the functional modules that are compatible and supported by the product for which the app is built. For more information about Freshworks products and their compatible modules, refer to the products-functional modules mapping.

The required modules on which the app can be deployed can be configured in manifest.json.

manifest.json changes

  1. platform-version: From the app’s root directory, navigate to manifest.json. Modify the platform-version value to 3.0.

  2. In manifest.json, remove the attribute product.

  3. Configure the modules attribute and specify all modules on which the app can be deployed.

    • If your app uses the app building capabilities such as common placeholders and serverless events (such as App set-up, External, or Scheduled events), interfaces that are not module-specific, request templates for placing secure HTTP calls or if you are migrating a serverless SMI app, configure the modules.common attribute.

      Important:

      If you are including the modules.common attribute, ensure that:

      • The app manifest must contain at least one module (apart from the common module).
      • Even if your app is built only for the common module, ensure that the modules object contains at least one module in addition to the common module. In this case, the other module(s) can be an empty JSON object. This other module’s name ties the app to the product on which the app is eventually deployed.
        {
      
          "modules": {
            "common": {
              "events": {
                "onAppInstall": {
                  "handler": "onAppInstallHandler"
                },
                "onAppUninstall": {
                  "handler": "onAppUninstallHandler"
                }
              },
              "requests": {
                "requestMethod1": {},
                "requestMethodName2": {}
              },
              "functions": {
                "serverMethodName1": {},
                "serverMethodName2": {}
              }
            },
            "support_ticket": {}
          },
      
        }
    • If your app uses module-specific app parameters such as placeholders or subscribe to product events through event listeners, configure the modules.<module_name> attribute.

        {
          ...
          "modules": {
            "common": {
              "events": {
                "onAppInstall": {
                  "handler": "onAppInstallHandler"
                },
                "onAppUninstall": {
                  "handler": "onAppUninstallHandler"
                }
              }
            },
            "support_ticket": {
              "location": {
                "ticket_sidebar": {
                  "url": "index.html",
                  "icon": "styles/images/icon.svg"
                }
              },
              "events": {
                "onTicketCreate": {
                  "handler": "onTicketCreateHandler"
                }
              }
            }
          }
          ...
        }
  4. If your app uses the request method to make secure HTTP calls to third-party domains, in manifest.json, list all request templates that the app intends to use. To do this, under modules.common, add the requests attribute of the following format.

    "requests": {
      "<requestTemplateName>":{},
      "<requestTemplateName1>": {}
    }

    Note:Ensure that the <requestTemplateName> is the same as that configured in config/requests.json.

  5. To migrate a serverless app, in manifest.json, under modules.common register the common events like app setup events, external events, or scheduled events, defined in server.js and the corresponding callback methods. If your app subscribes to product events, configure the events in the corresponding module.<module_name>.

    To do this, in manifest.json, add an attribute - events of the following format and list all serverless methods defined in the exports code block of the app’s server.js file:

    "modules": {
        "common": {
          "events": {
            "onAppInstall": {
              "handler": "onAppInstallHandler"
            },
            "onAppUninstall": {
              "handler": "onAppUninstallHandler"
            }
          }
        },
        "<module_name>": {
          "events": {
            "<productEvent1>": {
              "handler": "<productEvent1_Handler>"
            }
          }
        }
      }
  6. To migrate a serverless SMI app, in manifest.json, allowlist all SMI functions/methods defined in the serverless component of the SMI app. To do this, add an attribute - modules.common.functions of the following format and list all SMI functions (server methods) defined in the exports code block of the app’s server.js file:

    "functions": {
      "<serverMethodName1>": {},
      "<serverMethodName2>": {}
    }

App code changes

  1. If your app uses the default product’s stylesheet to build your app’s UI, navigate to the app project and in all front-end components’ HTML files (example: template.html, modal.html, and so on), replace the product's stylesheet with Freshworks stylesheet. To do this, replace the existing code to import the stylesheet as follows:

    Replace:

    <link rel="stylesheet" type="text/css" href="https://static.freshdev.io/fdk/2.0/assets/<product-specific-stylesheet>.css">

    With:

    <link rel="stylesheet" type="text/css" href="https://static.freshdev.io/fdk/2.0/assets/freshworks.css">
  2. If your app uses the data method - domainName to retrieve the domain name of the product account, you should modify the app.js file such that it uses the data method - currentHost. To do this, replace the existing code as follows:

    Replace:

    async function getDomainName() {
      try {
        const data = await client.data.get("domainName");
        // success operation
        console.log(data);
      } catch (error) {
        // failure operation
        console.error(error);
      }
    }
    
    getDomainName();

    With:

    async function getCurrentHostData() {
      try {
        const data = await client.data.get("currentHost");
        // success operation
      } catch (error) {
        // failure operation
      }
    }
    
    getCurrentHostData();

    Note:The payload retrieved will have the attributes, subscribed_modules that displays all the modules that the app user has subscribed to and present in the app manifest, and endpoint_urls that retrieves the product name and account url (domain name) to access the product resources. For more information, see currentHost.

  3. If your app uses the serverless payload attribute account_id to retrieve the identifier of the product account in the callback function, modify the server.js such that it ensures the use of currentHost to retrieve information about the supported modules, product names, and account URLs. Refer to the sample schemas shown below to understand the distinction.

    Payload schema for 3.0Payload schema for 2.3
    {
      "currentHost": {
        "subscribed_modules": [ "value" ],
        "endpoint_urls": {
          "freshdesk": "value"
        }
      },
      "data" : {
        //Contains the list of objects related to the event.
      },
      "event"  : "value",
      "iparams" : {
          "Param1" : "value",
          "Param2" : "value"
      },
      "region"  : "value",
      "timestamp"  : "value",
    }
    {
      "account_id" : "value",
      "data" : {
        //Contains the list of objects related to the event.
      },
      "domain" : "value",
      "event"  : "value",
      "iparams" : {
          "Param1" : "value",
          "Param2" : "value"
      },
      "region"  : "value",
      "timestamp"  : "value",
    }
  4. Eliminate the attributes data-bind and type_attributes to configure iparams as a JSON object if your app uses them to populate iparam fields. For global apps, note that for the attribute type, the values domain and api_key are not permissible.

    To retrieve the product-specific urls of the modules defined for the app and to access the product resources, use the data method - currentHost.

  5. If your app currently uses iparams to set values in the Settings page and you are migrating your app to a global app supporting multiple modules, you can use the modules attribute in the iparams.json to specify which module(s) the settings apply to. If this attribute is not present, the configured iparams are displayed on the Settings page, irrespective of the module on which the app is installed. For more information on configuring iparams for global apps, see Build an app settings page.

    Sample iparams.json file
      {
        "age": {
          "display_name": "Age",
          "description": "Please enter your age in years",
          "type": "number",
          "modules": ["support_ticket", "service_ticket" ]
        },
        "contactType": {
          "display_name": "Contact Type",
          "description": "Please select the contact type",
          "type": "dropdown",
          "modules": ["support_ticket" ],
          "options": ["Phone","Email"]
        }
      }

    Important: Ensure to use only the names of the modules registered in the App Manifest.

  6. When migrating to platform version 3.0, you can configure multiple OAuth configurations. It means, your app can access multiple OAuth-secured resources. Important: Currently, this feature lets your app access a maximum of three OAuth-secured resources. To configure all OAuth configurations that the app is expected to use as JSON objects, use the integrations attribute.

      {
        "integrations": {
          "<oauth_configuration_name1>": {
            "display_name": "value",
            "client_id": "value",
            "client_secret": "value",
            "authorize_url": "url value",
            "token_url": "url value",
            "options": {
              "scope": "read"
            },
            "token_type": "account or agent"
          },
          "<oauth_configuration_name2>": {
            "display_name": "value",
            "client_id": "value",
            "client_secret": "value",
            "authorize_url": "url value",
            "token_url": "url value",
            "options": {
              "scope": "read"
            },
            "token_type": "account or agent"
          }
        }
      }
  7. To construct a dynamic host value that is populated during runtime, though it is possible to retrieve the endpoint_urls through the currentHost data method or from the serverless payload attributes and pass it as contextual data, you should use the currentHost - enpoint_urls template substitution method. For more information on currentHost and endpoint_urls, see Global app concepts.