Guide to Convert Contentstack Extensions to Marketplace Apps

This comprehensive guide will help you convert your existing Contentstack extension to a marketplace app.

Note: Refer to the Difference Between Apps and Extensions document to know more about the difference between them.

Prerequisites

  • Contentstack account with Admin role to stack
  • Important: Get access to app-sdk repository from a concerned technical person of Contentstack
  • Development requirements:
    • Node.js - v14.18.2
    • Npm - v8.1.4
    • Your own development requirements, for example - React.js, Express.js, etc.
  • Rest API client, such as Postman

Steps to Convert Contentstack extension to a Marketplace App

  1. Remove Existing Extension from the Stack
  2. Develop your Marketplace App
  3. Create an App in Contentstack Developer Hub
  4. Update your Marketplace App via API Call
  5. Add a Webhook to your App (Optional)
  6. Install the App
  7. Use your App in Content Types and Entries

Let’s look at the steps in detail.

  1. Remove Existing Extension from the Stack

    The first step to convert an extension into a marketplace app is to remove your existing extension.
    Log in to your Contentstack account and perform the following steps to remove your extension:

    1. Click on the “Stacks” icon and select the stack where you’ve created your extension.
    2. Click on “Settings” and select “Extensions”. You’ll get a list of all the extensions that you’ve created.
    3. Hover over the extension that you want to convert into an app and click on the “Delete” icon.
    4. Click on “Delete” again to confirm your action.
  2. Develop your Marketplace App

    When developing a Marketplace app, there are two parts that you need to work on: frontend and backend.
    You can skip the backend part if your app doesn’t require server-side processing or logic. While the backend could be built in any programming language or framework of your choice, make sure to develop the frontend of your app in a JavaScript environment.
    This is to ensure that your app can communicate with the NPM module app-sdk .

    Let’s get started with the setup of your UI app.

    1. Create your app’s root directory.
    2. Open your terminal/ command line, and navigate to your app’s root directory.
    3. Run npm init to initialize your project to start using npm packages.
    4. Navigate to your app’s root directory via the terminal/ command prompt, and run this command to install app-sdk:
      npm install @contentstack/app-sdk
    5. Click on “Delete” again to confirm your action.
    6. First, you must initialize app-sdk using the following code snippet, you can also refer to the example section which is at the end of this documentation:
      ContentstackAppSdk.init().then(function (appSdk) {
                      // Add your UI logic here
      });
    7. A Marketplace app could have one or more UI locations in Contentstack. The UI locations are as follows:

      Note: In an extension, you can only add one Contentstack UI location.

      Please make sure that your UI app has the respective URL routing for the selected locations:

      Sl. No.Contentstack LocationYour app’s URL

      1.

      Custom Field

      https://{yourwebsite.com}/custom-field

      2.

      Sidebar Widget

      https://{yourwebsite.com}/sidebar-widget

      3.

      Dashboard Widget

      https://{yourwebsite.com}/dashboard-widget

      4.

      Config Screen

      https://{yourwebsite.com}/config

      4.

      RTE - Rich Text Editor

      https://{yourwebsite.com}/rte
  3. After successfully developing your app, deploy both the frontend and backend code of your app on any cloud platform of your choice and make a note of the URL where your app is hosted.
    Based on the location, your app’s Base URL will change accordingly.
    For example, if you're building a marketplace app for a custom field, your Base URL will look like this: https://{yourwebsite.com}/custom-field.
    Contentstack will then render this URL on its webpage.

  4. Creating an App in Contentstack Developer Hub

    It’s time to put your newly built app into action.
    Connect your deployed app to Contentstack. For that, you need to create a Marketplace app.

    To create an app in Marketplace, perform the steps given in the Create an App in Marketplace document.

    Once done, your Marketplace app is now ready.

  5. Update your Marketplace App via API Call

    In this step, you need to pass important details such as Organization UID and your Authtoken to Contentstack.
    These details will allow your app to work in a custom way and make it installable.

    Note: If your app needs a webhook, then this step will work in conjunction with the next step, so please make sure that your API call contains the configuration of your webhook.

    Now, let’s see how to update the app with the necessary details:

    1. While you're on the basic information page of your app, open “Developer Tools” of your browser (accessible under the “More tools” option of the respective browser), and go to the “Network” tab. 
      Developer_Tools_Network.png
    2. Refresh your browser page, and search and click on user?include_orgs=true (probably the seventh request). 
      User_Request.png
    3. Under Preview of user?include_orgs=true request’s response, note down the value for user.authtoken.
      Also, note down the organization’s UID where your app should be installed, this will be in user.organizations array. 
      Preview_Of_User_Response.png
    4. Open Postman, and pass the following details in your API call:
      • Pass the App UID that you noted in the above step instead of APP_UID in the URL
      • Pass the authtoken and organization_uid values in Headers
      • Pass the name and base_url in the body of your API call.

      If some of the UI locations are not required, then you can remove them from the request’s body.locations array.

    URL: https://developerhub-api.contentstack.com/apps/{{APP_UID}}
    
    HTTP Method: PUT
    
    Headers: {
            authtoken: <auth_token_of_contentstack_account>,
            organization_uid: <uid_of_organization>
        }
    
    
    Body(JSON): {
            "name": "<NEW_APP>",
            "target_type": "stack",
            "ui_location": {
                "locations": [
                    {
                        "type": "cs.cm.stack.config",
                        "meta": [
                            {
                                "signed": true,
                                "path": "/config"
                            }
                        ]
                    },
                    {
                        "type": "cs.cm.stack.sidebar",
                        "meta": [
                            {
                                "signed": true,
                                "path": "/sidebar-widget"
                                "data_type": "json"
                            }
                        ]
                    },
                    {
                        "type": "cs.cm.stack.dashboard",
                        "meta": [
                            {
                                "signed": true,
                                "path": "/dashboard-widget"
                                "data_type": "json"
                            }
                        ]
                    },
                    {
                        "type": "cs.cm.stack.custom_field",
                        "meta": [
                            {
                                "signed": true,
                                "path": "/custom-field",
                                "data_type": "json"
                            }
                        ]
                    },
                    {
                        "type": "cs.cm.stack.rte",
                        "meta": [
                            {
                                "signed": true,
                                "path": "/rte"
                                "data_type": "json"
                            }
                        ]
                    }
                ],
                "signed": true,
                "base_url": "https://YourWebsite.com"
            }
        }
    
  6. Add a Webhook to your App (Optional)

    This step would probably be an optional one for most of the instances. So, if your app needs a webhook, this step will be in conjunction with step 4 above. Thus, it’s advised to update your app via an API call carefully.

    Note: To know more about webhooks and their relation with Contentstack, please refer to the Set up Webhooks section.

    Now let’s add a webhook to your app:

    1. Add the following key-value pair (as a value) inside the data key, to the JSON body of the API call that you used in Step 4, so it becomes data.webhook:
      "webhook": {
          "signed": true,
          "name": "<Your App Name>",
          "enabled": true,
          "target_url": "https://webhook.site/5ec29kc0-9ppa-495a-9e4f-701b63892bhe",
          "channels": [
              "content_types.entries.environments.publish.success",
              "assets.environments.publish.success",
              "content_types.entries.environments.unpublish.success",
              "assets.environments.unpublish.success",
              "content_types.entries.delete",
              "content_types.delete",
              "assets.delete"
          ],
          "app_lifecycle_enabled": true,
          "retry_policy": "manual"
      }
    2. Change the value of webhook.name to your app’s name, and the value of webhook.target_url to your app’s target URL.
    3. Change the value of the webhook.channels array as per your requirement.
      You can refer to the Webhook Events documentation to know more about webhook.channels.

    After modifying all the above values, hit the API call. You should get the status “200 OK”. After your app is installed, your configuration screen should be displaying a new Webhook Logs section as shown below

    Webhook_Logs.png
  7. Install the App

    Now let’s install the Marketplace app in one of your stack.

    To install your app, perform the steps covered in the Installing an App in Developer Hub guide.

    Once done, your app is now installed and ready to use.

  8. Use your App in Content Types and Entries

    Once your app is installed, navigate to the respective UI locations and check the rendering of your app in its defined locations.
    For example, if your app has a “Custom Field” location, let’s see how you can use it in your content type:

    1. Navigate to the stack where the app is installed.
    2. Create a content type with the custom field or add your app as a custom field in your existing content type.
    3. Finally, start add an entry for that content type using your app.

    Similarly, test the app in other locations where you have installed it.

    This concludes the setup guide of converting Contentstack extensions to marketplace apps.

Example Code Featuring extension-sdk and app-sdk

Let’s say you have an extension with a custom field as its UI location, and it stores some data in Contentstack and retrieves it back. You need to convert it into its corresponding Marketplace app.

Here’s a simple extension that stores and retrieves some data from/ to Contentstack:

<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<script src="https://www.contentstack.com/sdks/contentstack-ui-extensions/dist/latest/ui-extension-sdk.js"></script>
<link href="https://www.contentstack.com/sdks/contentstack-ui-extensions/dist/latest/ui-extension-sdk.css" rel="stylesheet" type="text/css" media="all">
</head>
<body>
    <input type="text" id="input1" onchange="onChange()" onclick="setFocus()">
    <script>
        // initialize Field Extension
        window.extensionField = {};
        
        // find color input element
        var HtmlElement = document.getElementById("input1");
        
        ContentstackUIExtension.init().then(function(extension) {
            // make extension object globally available
            extensionField = extension;
            
            // update the field height 
            extensionField.window.updateHeight();
            
            // Get current color field value from Contentstack and update the color picker input element
            HtmlElement.value = extensionField.field.getData();
        })
        
        // on click of element we will set setFocus on field
        function setFocus(){
            extensionField.field.setFocus();
        }
        
        // On color change event, pass new value to Contentstack
        function onChange(){
            extensionField.field.setData(HtmlElement.value);
        }
        
    </script>
</body>
</html>

Here’s the code for the Marketplace app built using React.js with TypeScript, for the above extension:

import React, { useEffect, useState } from 'react';
 
import ContentstackAppSdk from '@contentstack/app-sdk';
import { isEmpty } from 'lodash';
 
import { TypeDataSDK } from '../../common/types';
 
import InputElement from '../../components/inputelement/index';
 
const CustomField: React.FC = function () {
   const [state, setState] = useState<TypeDataSDK>({
       config: {},
       location: {},
       appSdkInitialized: false,
   });
 
   const [inputData, setInputData] = useState<String>('');
 
   useEffect(() => {
       ContentstackAppSdk.init().then(async appSdk => {
           const config = await appSdk?.getConfig();
 
           setState({
               config,
               location: appSdk.location,
               appSdkInitialized: true,
           });
 
           appSdk.location.CustomField?.frame.updateHeight(300);
 
           const initialData = appSdk.location.CustomField?.field.getData();
 
           if (initialData && !isEmpty(initialData)) {
               setInputData(initialData);
           }
       });
   }, []);
 
   const onChangeSave = (saveData: any) => {
       state.location?.CustomField?.field?.setData(saveData.toString());
   };
 
   return (
       <div>
           {state.appSdkInitialized && (
               <InputElement onChange={onChangeSave} value={inputData} />
           )}
       </div>
   );
};
 
export default CustomField;
 

Was this article helpful?

Thanks for your feedbackSmile-icon

On This Page

^