# Building Marketplace Apps

### About this export

| Field | Value |
| --- | --- |
| **content_type** | course |
| **platform** | contentstack-academy |
| **source_url** | https://www.contentstack.com/academy/courses/marketplace-apps |
| **language** | en |
| **product_area** | marketplace |
| **learning_path** | contentstack-developer-certification |
| **course_id** | marketplace-apps |
| **slug** | marketplace-apps |
| **version** | 2026-03-01 |
| **last_updated** | 2026-04-28 |
| **status** | published |
| **keywords** | ["marketplace"] |
| **summary_one_line** | Welcome to Introduction to Marketplace Applications Course! This course is intended to introduce developers to the process of creating new Marketplace Apps and installing them in Contenstack. Required for this course: 1.… |
| **total_duration_minutes** | 65 |
| **lessons_count** | 16 |
| **video_lessons_count** | 0 |
| **text_lessons_count** | 16 |
| **linked_learning_path** | contentstack-developer-certification |
| **linked_assessment_ref** | LMS_UNCONFIGURED_COURSE_ASSESSMENT |
| **markdown_file_url** | /academy/md/courses/marketplace-apps.md |
| **generated_at** | 2026-04-28T06:55:36.759Z |
| **intended_audience** | [] |
| **prerequisites** | [] |
| **related_courses** | [] |

> **Academy MD v3** — companion `.md` for Ask AI. Quizzes and graded assessments are **LMS-only**; this file never contains answer keys.

## Course Overview

| Metadata | Value |
| --- | --- |
| Catalog duration | 1h 5m 5s |
| Released (if known) | 2026-03-01 |
| Product area | marketplace |

### Description

### **Welcome to Introduction to Marketplace Applications Course!**

  

This course is intended to introduce developers to the process of creating new Marketplace Apps and installing them in Contenstack. 

Required for this course:

1.  Account in Contentstack with access to an Organization as an Administrator or Developer. Create a training Org/Stack [here](https://www.contentstack.com/academy/). Training stacks are available for 25 days. Alternatively, you can bootstrap a [Starter App via the CLI](https://www.contentstack.com/docs/developers/cli/bootstrap-starter-apps) or from the [Marketplace](https://app.contentstack.com/#!/marketplace/starters). 
    
2.  Permissions to create new Marketplace Apps in Developer Hub.
    
3.  Permissions to install Developer Hub Applications in your stack. 
    
4.  Assets for all the walkthroughs can be found on [Github](https://github.com/contentstack/marketplace-academycourse-app).
    
5.  Contentstack Community account. If you do not have one, you can visit [community.contentstack.com/](https://community.contentstack.com/) and register for a new account. You can adjust your email notifications under your profile at [https://community.contentstack.com/settings/general](https://community.contentstack.com/settings/general) **﹥ Settings.**

### Overview

### **About the Course  
**

**What this course is:**

*   A comprehensive guide for developers aiming to understand the intricacies of building marketplace apps.
    
*   A deep dive into understanding the unique benefits of various extension locations, their specific use cases, common pitfalls, and best practices.
    
*   A blend of general guidance and in-depth insights on recommended approach when building Contentstack extensions via marketplace apps.
    
*   Not just any guide, but a set of step-by-step tutorials that walk the developer through the process of setting up a marketplace development environment using a boilerplate application that contains the foundation of any marketplace app development project.
    

  

**What this course is not:**

*   A tutorial on developing the front end of your digital properties.
    
*   A guide on leveraging Contentstack Management or Content Delivery APIs for content management and delivery.
    
*   An exhaustive course that delves into every minute detail related to software development lifecycle. There are advanced development concepts that are beyond the scope of what this course covers.
    

  

**\*Note:** Contentstack is a headless CMS with an API-first approach. Additionally Contentstack is the only fully automated Composable DXP, and due to this nature, several marketplace apps are available at the customer's disposal to integrate with various technologies and services. Have a look at the [Marketplace Apps](https://www.contentstack.com/docs/developers/marketplace-apps) documentation to get started.

### Learning objectives

1. Follow each lesson in order.
2. Practice in a training stack using placeholders **YOUR_STACK_API_KEY** and **YOUR_DELIVERY_TOKEN** in local `.env` files only.
3. Validate API responses against the official documentation.

### Topics covered

marketplace

## Course structure

```text
marketplace-apps/
├── 01-introduction-to-marketplace-apps-and-app-framework · text · 1 min
├── 02-marketplace-boilerplate · text · 1 min
├── 03-development-environment-setup-for-marketplace · text · 1 min
├── 04-creating-app-in-developer-hub-and-installing-the-app · text · 1 min
├── 05-implementing-app-locations-introduction · text · 1 min
├── 06-implementing-app-locations-app-configuration · text · 1 min
├── 07-implementing-app-locations-custom-field · text · 1 min
├── 08-implementing-app-locations-entry-sidebar · text · 1 min
├── 09-implementing-app-locations-stack-dashboard · text · 1 min
├── 10-implementing-app-locations-asset-sidebar · text · 1 min
├── 11-implementing-app-locations-full-page · text · 1 min
├── 12-implementing-app-locations-field-modifier · text · 1 min
├── 13-implementing-app-locations-json-rte · text · 1 min
├── 14-best-practices-tips-and-next-steps · text · 1 min
├── 15-summary · text · 3 min
├── 16-marketplace-apps-quiz · quiz (LMS only) · 3 min
```

## Lessons

### Lesson 01 — Introduction to Marketplace Apps and App Framework

<!-- ai_metadata: {"lesson_id":"01","type":"text","duration_minutes":1,"topics":["Introduction","Marketplace","Apps","and","App","Framework"]} -->

#### Lesson text

This course will help customers, partners and developers create a marketplace application from scratch.

This course covers the different areas of integration in Contenstack’s UI. By integrating your own functionality in these areas or “locations”, you can address different use cases and add functionality in the platform, according to your business needs. The Marketplace App framework provides a seamless way to directly integrate with Contentstack’s UI.

Additionally, Marketplace applications provide one-click install, simplifying the setup and distribution of your application across different stacks.

Marketplace Applications leverage the App Framework, a module which can be used to develop a new marketplace app. The framework is available as a npm package under @contentstack/app-sdk. The package can be installed by developers using npm or yarn (or any other package manager of their choosing that supports node packages) as they would normally do when building other react or node apps. This framework provides access to various data points when loaded in specific locations as mentioned in the previous section. The \[Contentstack App SDK\]([https://github.com/contentstack/app-sdk-docs](https://github.com/contentstack/app-sdk-docs)) also provides necessary methods to interact with the Contentstack application.

By the end of this course. participants will be able to develop and deploy an example marketplace application that illustrates how you can extend those UI areas.

#### Key takeaways

- Connect **Introduction to Marketplace Apps and App Framework** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 02 — Marketplace Boilerplate

<!-- ai_metadata: {"lesson_id":"02","type":"text","duration_minutes":1,"topics":["Marketplace","Boilerplate"]} -->

#### Lesson text

In computer programming, boilerplate code, or simply boilerplate, are sections of code that are repeated in multiple places with little to no variation. Boilerplate templates or frameworks provide a starting point for building new solutions that follow a given pattern, require repetitive tasks, access to certain SDKs, and that reuse certain pieces of code. Boilerplate code or applications can define project-level elements or standard methods for one or more projects.

Contentstack provides a boilerplate that incorporates reference examples developers can use when building UI integrations in Contentstack.

This boilerplate reduces the effort of developers when building their integrations. It also provides more visibility on the structure of the marketplace applications.

The boilerplate code includes all the locations where your integrations can be placed inside Contentstack’s UI. Those locations are:

*   Custom Field
    
*   Entry Sidebar Widget
    
*   Asset Sidebar Widget
    
*   Dashboard Widget
    
*   Field Modifier
    
*   Full Page
    
*   JSON RTE plugin 
    

The following modules in this course describe how to build an app using our Marketplace App boilerplate (which uses app sdk for interaction with Contentstack). For more information about the Marketplace App boilerplate, you can check the [Marketplace App Boilerplate GitHub repository](https://github.com/contentstack/marketplace-academycourse-app).

#### Key takeaways

- Connect **Marketplace Boilerplate** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 03 — Development Environment Setup for Marketplace

<!-- ai_metadata: {"lesson_id":"03","type":"text","duration_minutes":1,"topics":["Development","Environment","Setup","for","Marketplace"]} -->

#### Lesson text

In this module we will describe how to set up your development environment, that is, clone our boilerplate application and get you ready to start developing your first marketplace application.

## Pre-requisites

Before setting up the development environment in your local machine, you must make sure the below software is installed:

1\. [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) - v8.1.4 or greater

2\. [NodeJS](https://nodejs.org/en/download) - v14.18.2 or greater  
Then, [Download or clone the Marketplace app boilerplate repo from our GitHub repo.](https://github.com/contentstack/marketplace-academycourse-app)

The boilerplate application consists of the following file structure:

## **Project Folder Structure**

There are two main working directories. The project root, which is used for all Locations except for JSON RTE. The JSON RTE plugin logic is managed and developed within **\[root\]/rte.**

└── marketplace\-academycourse\-app **//Main Project Directory**  
├── CODEOWNERS  
├── LICENSE  
├── README.md  
├── SECURITY.md  
├── manifest.json  
├── package\-lock.json  
├── package.json  
├── public  
│ ├── favicon.ico  
│ ├── index.html  
│ ├── logo192.png  
│ ├── logo512.png  
│ ├── manifest.json  
│ └── robots.txt  
├── rte **//Main RTE Plugin directory**  
│ ├── CODEOWNERS  
│ ├── LICENSE  
│ ├── README.md  
│ ├── SECURITY.md  
│ ├── docs  
│ │ ├── api\-reference.md  
│ │ └── images  
│ │ ├── BlockLeaf.png  
│ │ ├── BlockTypes.png  
│ │ ├── Dropdown.jpg  
│ │ ├── Path.png  
│ │ ├── Point.png  
│ │ └── Range.png  
│ ├── package\-lock.json  
│ ├── package.json  
│ ├── src  
│ │ ├── plugin.tsx  
│ │ ├── solution\-1.txt  
│ │ └── solution\-2.txt  
│ ├── tsconfig.json  
│ ├── webpack.academy.js  
│ ├── webpack.common.js  
│ ├── webpack.dev.js  
│ ├── webpack.prod.js  
│ └── yarn.lock  
├── src  
│ ├── assets  
│ │ ├── Field\_Modifier.svg  
│ │ ├── Icon.svg  
│ │ ├── appconfig.svg  
│ │ ├── assetsidebar.svg  
│ │ ├── customfield.svg  
│ │ ├── fullScreenGraphics.svg  
│ │ ├── fullscreen.svg  
│ │ └── sidebarwidget.svg  
│ ├── common  
│ │ ├── contexts  
│ │ │ ├── appConfigurationExtensionContext.ts  
│ │ │ ├── customFieldExtensionContext.ts  
│ │ │ ├── entrySidebarExtensionContext.ts  
│ │ │ └── marketplaceContext.ts  
│ │ ├── hooks  
│ │ │ ├── useAppConfig.ts  
│ │ │ ├── useAppLocation.ts  
│ │ │ ├── useAppSdk.tsx  
│ │ │ ├── useCustomField.tsx  
│ │ │ ├── useEntry.tsx  
│ │ │ ├── useFrame.ts  
│ │ │ ├── useHostUrl.ts  
│ │ │ ├── useInstallationData.tsx  
│ │ │ └── useSdkDataByPath.ts  
│ │ ├── locales  
│ │ │ └── en\-us  
│ │ │ └── index.ts  
│ │ ├── providers  
│ │ │ ├── AppConfigurationExtensionProvider.tsx  
│ │ │ ├── CustomFieldExtensionProvider.tsx  
│ │ │ ├── EntrySidebarExtensionProvider.tsx  
│ │ │ └── MarketplaceAppProvider.tsx  
│ │ ├── types  
│ │ │ └── types.ts  
│ │ └── utils  
│ │ └── functions.ts  
│ ├── components  
│ │ ├── AppFailed.tsx  
│ │ └── ErrorBoundary.tsx  
│ ├── containers  
│ │ ├── 404  
│ │ │ └── 404.tsx  
│ │ ├── App  
│ │ │ └── App.tsx  
│ │ ├── AssetSidebarWidget  
│ │ │ ├── AssetSidebar.tsx  
│ │ │ ├── solution.txt  
│ │ │ └── styles.scss  
│ │ ├── ConfigScreen  
│ │ │ ├── AppConfiguration.tsx  
│ │ │ └── solution.txt  
│ │ ├── CustomField  
│ │ │ ├── CustomField.tsx  
│ │ │ ├── solution\-1.txt  
│ │ │ ├── solution\-2.txt  
│ │ │ └── styles.scss  
│ │ ├── DashboardWidget  
│ │ │ ├── StackDashboard.tsx  
│ │ │ ├── solution.txt  
│ │ │ └── styles.scss  
│ │ ├── FieldModifier  
│ │ │ ├── FieldModifier.tsx  
│ │ │ ├── solution.txt  
│ │ │ └── styles.scss  
│ │ ├── FullPage  
│ │ │ ├── FullPage.tsx  
│ │ │ ├── solution.txt  
│ │ │ └── styles.scss  
│ │ ├── SidebarWidget  
│ │ │ ├── EntrySidebar.tsx  
│ │ │ ├── solution.txt  
│ │ │ └── styles.scss  
│ │ └── index.tsx  
│ ├── index.css  
│ ├── index.tsx  
│ ├── react\-app\-env.d.ts  
│ └── reportWebVitals.ts  
└── tsconfig.json  
   

**Tip**: For each exercise in this course, you can access the "solution" code for each modified file in their respective folders within the files named as _solution.txt or solution-X.txt_. Those files can be used to fix any issues or missed steps while performing the exercises.

## **Dependency Installation and Application Run**

Using your terminal or command prompt, navigate to the root directory of the downloaded repo and execute the following commands:

npm install  
  
npm start

The first command will install all the npm dependencies. Once dependencies are installed, execute the second command to start the development server. The server will start in the port 3000, http://localhost:3000

In this boilerplate the JSON RTE plugin can be started and tested as a separate service. It can not be started along with the react app that we use for other UI locations. To start the JSON RTE plugin service, follow below steps.

To setup and run the JSON RTE plugin example, using your terminal, navigate to the /rte directory of the downloaded repository and execute the following commands:

npm install  
  
npm start

Like before, the first command will install the npm dependencies and the second command will start the development server. The server will start in the port 3000 http://localhost:3000

Most of the development in this course will take place within the container folder located at: **src/containers.**

Below are the containers that are available in the boilerplate:

*   **404** - This is just an example react component that can be used to render in the UI during 404 errors.
    
*   **App** - This is the top level container for the react app that is getting rendered in the index.tsx of react app. This contains definitions of all the routes that are available in our app.
    
*   **AssetSidebarWidget** - This container has the component that gets rendered in the asset sidebar UI location of our app. This can be used for reading asset data and updating its properties
    
*   **ConfigScreen** - This container has the component that gets rendered in the configuration UI location of our app. All the credentials needed to integrate the 3rd party system should be updated here.
    
*   **CustomField** - This container has the component that gets rendered in the custom field UI location. This is the major location where the data from 3rd party systems gets added into the entry of Contentstack.
    
*   **DashboardWidget** - This container has the component that gets rendered in the Dashboard UI location of the stack. This location can be used to display analytics data of the stack.
    
*   **FieldModifier** - This container has the component that gets rendered in the Field Modifier UI location. This location is available at each field level in the entry page. This can be used to extend capabilities of entry fields , so that apps can appear only on defined data types.
    
*   **FullPage** - This container has the component that gets rendered in the Full Page UI location. This location can be used for doing bulk activities on entries.
    
*   **SidebarWidget** - This container has the component that gets rendered in the SidebarWidget UI location. This can be used to display more detailed information of the data of a 3rd party system or can even be used to update workflow or actual data of entries.
    

## **JSON RTE PLUGINS**

The JSON RTE Plugin will be implemented in the **rte/src.** A couple scripts are provided to build and deploy the compiled plugin code into the create-react-app **public** folder so it can be served from there.

## **Video: Development Environment Setup**

The following video walks you through the process of downloading the boilerplate app, and preparing your code editor, so you can perform the exercises in the following modules.

#### Key takeaways

- Connect **Development Environment Setup for Marketplace** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 04 — Creating App in Developer Hub and Installing the App

<!-- ai_metadata: {"lesson_id":"04","type":"text","duration_minutes":1,"topics":["Creating","App","Developer","Hub","and","Installing"]} -->

#### Lesson text

In this section you will register your app in Developer Hub.

The Contentstack Developer Hub is an app development framework/portal that developers can leverage to rapidly build, host, and publish ready-to-use private or public apps. For more information about Developer Hub, please refer to the [Developer Hub documentation](https://www.contentstack.com/docs/developers/developer-hub).

**Note:** To complete this module is mandatory that you have your application running locally in Visual Studio Code, so make sure that you run the npm start command and that your application is running and browsable in port 3000**,**

  

To confirm your application is running, go to http://localhost:3000. you should see the following screen:  
![BuildingMarketplaceApps\_L1\_img-1.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt9baf8e24ad9fac41/67ddd1beefd8a977054c06d5/BuildingMarketplaceApps_L1_img-1.png)  

  

Next, you will access the Developer Hub and register your application. To do so, follow these steps:

1\. [Log in to your Contentstack account](https://www.contentstack.com/login/).

2\. In the left-hand-side primary navigation, you will find the Developer Hub section (as shown below). Click the icon to go to the Developer Hub.

![BuildingMarketplaceApps\_L4\_img-2.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt80ff7d7c1ff2bc95/67ddd1da782bfb1d246f5b22/BuildingMarketplaceApps_L4_img-2.png)

3\. Click the **+New App** button at the top right corner of the page.

4\. In the New App modal, provide a name and a description for your application. E.g. **Sample App.**

Notice this type of applications are considered private as they will not be publicly available in Contentstack’s marketplace.

![BuildingMarketplaceApps\_L4\_img-3.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt584d5bc4b9b2c57c/67ddd1f3983a65221b3b8134/BuildingMarketplaceApps_L4_img-3.png)

5\. Next, click Create.

6\. Next you will be presented with your application’s Information Page. Here you get access to your application’s basic information, and optionally you can upload an icon for your application. Once you are satisfied with the application’s name, description and icon, click Save to save your application’s data. 

![BuildingMarketplaceApps\_L4\_img-4.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltb20872c72a9d764f/67ddd211c566eea1c65e05ac/BuildingMarketplaceApps_L4_img-4.png)

7\. Next you will register all the UI locations this boilerplate application implements. To do so, you will click the UI Locations section on the left navigation bar. This will take you to the UI Locations Page.

8\. In the UI Locations Page, enter the App URL in the input field. The App URL is the url where your application is hosted. Since we are running the boilerplate locally on port 3000, you will enter the following value in the App URL field: _http://localhost:3000_

![BuildingMarketplaceApps\_L4\_img-5.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltca8399c374d7fda5/67ddd229c566ee43de5e05b0/BuildingMarketplaceApps_L4_img-5.png)

  

9\. Next we will register the different UI locations supported by the Boilerplate Application. In the following picture you can see a list of all the different supported extension locations.

![BuildingMarketplaceApps\_L4\_img-6.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blte073ab35b2984bbd/67ddd243443bd642a3f1d063/BuildingMarketplaceApps_L4_img-6.png)

10\. Add the following locations by clicking the **+Add** button that shows up when you mouse over the different Available Location(s), e.g:

![BuildingMarketplaceApps\_L4\_img-7.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt54fc7725493b90a5/67ddd258632b9325acd4a744/BuildingMarketplaceApps_L4_img-7.png)  
 **Note**: The name for each UI Location is optional, and can be used to override the default app name. Once you provide the necessary data as described below, click the _Save_ button at the bottom right corner of the page.

## **Custom Field**

**Name:** Sample App   
**Path:** /custom-field  
**Data Type:** JSON    
**Multiple:** checked  
**Enabled**: on

![BuildingMarketApps\_L4\_img-8.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt0660fcf65e5af419/67ddd27cf7eccc8cb390dd93/BuildingMarketApps_L4_img-8.png)

###   
**Stack Dashboard**  

**Name:** Sample App  
**Path:** /stack-dashboard  
**Default Width:** Full Width  
**Enabled**: on  

![BuildingMarketApps\_L4\_img-9.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt2801a882269d8baa/67ddd295cb02417c5f41baf5/BuildingMarketApps_L4_img-9.png)

  

## **Asset Sidebar**

**Name:** Sample App  
**Path:** /asset-sidebar  
**Enabled**: on  
**Width:** 500

![BuildingMarketApps\_L4\_img-10.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt465d51e71080deaf/67ddd2ae37e25a0c8b1e4be3/BuildingMarketApps_L4_img-10.png)

  
**App Configuration**

**Path**: /app-configuration  
**Enabled**: on

![BuildingMarketApps\_L4\_img-11.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltf7a9e25a026d071e/67ddd2c145b2297837911ba8/BuildingMarketApps_L4_img-11.png)

  

## **JSON RTE**

**Name**: Sample App  
**Path**: /json-rte.js  
**Enabled**: on

![BuildingMarketApps\_L4\_img-12.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt998120cf9e693e24/67ddd3535e486df27471bb8b/BuildingMarketApps_L4_img-12.png)

## **Entry Sidebar**

**Name**: Sample App  
**Description**: Example JSON RTE  
**Path**: /entry-sidebar  
**Enabled**: on

![BuildingMarketApps\_L4\_img-13.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt38f2afe06ed1a224/67ddd3683a0f383e7e00f05f/BuildingMarketApps_L4_img-13.png)

## **Field Modifier**

**Name**: Sample App  
**Path**: /field-modifier  
**Allowed Field Types**: All  
**Enabled**: on

![BuildingMarketApps\_L4\_img-14.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt9c735c8481833e23/67ddd37ea6d88e2861053a14/BuildingMarketApps_L4_img-14.png)

## **Full Page**

**Name**: Full Page Example  
**Path**: /full-page  
**Enabled**: on

For full page integrations, this location supports the ability to upload an icon. This icon is then displayed in the main left navigation icon list.

![BuildingMarketApps\_L4\_img-15.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt8218c8dd05f02172/67ddd3930c6f554d721fd454/BuildingMarketApps_L4_img-15.png)

Make sure that all the locations have been added and that you clicked "Save" for each of them. You should see the following in your **Enabled Location(s)** list:

![BuildingMarketApps\_L4\_img-16.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltfec1818564c23a2e/67ddd3a845b229ab4b911bb0/BuildingMarketApps_L4_img-16.png)

11.  Next you will install this application on a Stack. To do so, click the “Install” button on the top right corner of the screen.

![BuildingMarketApps\_L4\_img-17.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt9463c65f9c2664d9/67ddd3c15e486d864171bb91/BuildingMarketApps_L4_img-17.png)

  

12\. Next, Select the stack where you want to install the app and click the Install button.

![BuildingMarketApps\_L4\_img-18.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt7d2a83f254f6b4bf/67ddd3d7a6d88e5250053a1b/BuildingMarketApps_L4_img-18.png)

**Note:** make sue you accept the Terms of Service, and click _Install_.

13\. You will be redirected to the configuration page of the app. This is the App configuration location we registered earlier. At this time, no configuration needs to be provided here, but for a real world marketplace app, this is where developers would provide input fields and configure their application. For now you will see an empty configuration screen like this:

![BuildingMarketApps\_L4\_img-19.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blteedfa72cad67f1de/67ddd40cc9b8d450e7d14779/BuildingMarketApps_L4_img-19.png)

14\. Click **Save** at the bottom right corner.

15\. Next, click the _Open Stack_ button.

  

Congratulations! You registered and installed your first Marketplace App using Automation Hub.  
In the following modules you will learn how to modify the logic inside the different locations and add your own ui elements and functionality.

We will provide example code and will guide you step by step.

Additionally, here are some screenshots of examples of configuration screens for different apps available in Contentstack’s marketplace:

#### Key takeaways

- Connect **Creating App in Developer Hub and Installing the App** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 05 — Implementing App Locations Introduction

<!-- ai_metadata: {"lesson_id":"05","type":"text","duration_minutes":1,"topics":["Implementing","App","Locations","Introduction"]} -->

#### Lesson text

In this module you will take a look at the source code and do some modifications and you will see those changes taking effect in the UI as you make them.

The marketplace boilerplate app is based on React, more specifically a Client-Side-Rendered app. You can learn more about React’s standard setup by looking at the create-react-app template in which this boilerplate is based.

The React app rendering starts from the file “/src/index.tsx”. In that file, the App component is imported and wrapped with Browser Router which handles routing for the application, and allows you to navigate through the different locations registered before.

Next, the App component is defined at “/src/containers/App/App.tsx”. Here all the registered routes that are needed for the app are defined. Notice how the urls here match the ones used while registering them in Developer Hub.

**Best Practice**: Instead of directly importing the components of each route, you can import inside React.lazy() and wrap it inside the component of react for better performance of the app. This will improve the initial loading time of the application in the browser making the components load lazily for routes as needed and only when requested.

The following code snippet illustrates this concept:

const AppConfigurationExtension = React.lazy(() => import("../ConfigScreen/AppConfiguration")); ... <Routepath\="/uri-endpoint-for-your-route"element\={<Suspense\><NewProviderOfTheRoute\><NewComponentOfTheRoute /></NewProviderOfTheRoute\></Suspense\>}/>;
  

In the following modules, you will browse through the available UI locations along with their corresponding routes and code.

#### Key takeaways

- Connect **Implementing App Locations Introduction** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 06 — Implementing App Locations: App Configuration

<!-- ai_metadata: {"lesson_id":"06","type":"text","duration_minutes":1,"topics":["Implementing","App","Locations","App","Configuration"]} -->

#### Lesson text

The App Config UI Location allows you to manage all the settings for your app centrally. You need to configure it once and all the other locations (where the app is installed) can access these configurations.

The app configuration page is a separate entity that allows you to configure your application. Setting up an app configuration page allows you to store all the config settings for your application and secure their access from a single location.

## **Use Case**

Imagine we are going to build an e-commerce app to integrate an e-commerce platform with Contentstack. Normally, these applications offer different configuration parameters that need to be saved and used to communicate with them.  

You can leverage the app configuration UI Location to manage those parameters, such as API Key, Store Id, Channels, Locales...

To save these app configuration values, you have to use the app sdk framework. You can refer to the code below which initializes the app sdk framework and gets the config object from the app sdk and sets it into a state variable.

This state variable can be used in future for displaying in the UI Component of configuration screen. Here you can find an example code snippet:

import ContentstackAppSdk from "@contentstack/app-sdk";  
   
const \[state, setState\] \= useState<TypeAppSdkConfigState\>({  
installationData: {  
configuration: {},  
serverConfiguration: {},  
},  
setInstallationData: (): any \=> {},  
appSdkInitialized: false,  
});  
   
useEffect(() \=> {  
ContentstackAppSdk.init()  
.then(async (appSdk) \=> {  
const sdkConfigObject \= appSdk?.location?.AppConfigWidget?.installation;  
if (sdkConfigObject) {  
const installationDataFromSDK \=  
await sdkConfigObject.getInstallationData();  
const setInstallationDataOfSDK \= sdkConfigObject.setInstallationData;  
setState({  
...state,  
installationData: installationDataFromSDK,  
setInstallationData: setInstallationDataOfSDK,  
appSdkInitialized: true,  
});  
}  
})  
.catch((error) \=> {  
console.error("appSdk initialization error", error);  
});  
}, \[\]);  
 

The configuration values will be available in the **installationData** attribute of the object that we get from appSdk object, too.

The **installationData** is an object which has two keys in it:

1.  **configuration** - This should have all the insensitive configs of an app as this will be available in all the other UI locations.
    
2.  **serverConfiguration** - This should have all the sensitive / critical information= of the configs. This will only be sent through webhooks. Users of the app will never get these values in other UI locations.
    

Once the configurations are set using the code above, whenever the user (installer) changes any values in the configuration screen, **state.installationData()** can be called by passing updated **configuration** and **serverConfiguration** values as shown below. That way the app sdk saves the updated values for this installation, persisting them for the application to use when loading in the different locations. Here is a code snipped to do so:

state.setInstallationData({  
configuration: updatedConfigurationObject,  
serverConfiguration: updatedServerConfigurationObject,  
});  
 

## **Exercise 1**

In this exercise, you will add a form with an input box to your configuration screen. Then you will capture the text entered in such input field and store it in Contentstack's application configuration. The value will be persisted and will be displayed in the input field every time you access the configuration screen so users can manage it.

*   Using your code editor, access the file located at: **src/containers/ConfigScreen/AppConfiguration.tsx**
    
*   Import the following react components from the Contenstack venus components library:
    

1.  import {  
    Field,  
    FieldLabel,  
    Form,  
    TextInput,  
    } from "@contentstack/venus-components";  
     
    

*   Copy the following JSX snipped and replace the _{/\* add form code here \*/}_ placeholder with it:
    

1.  <Form className\="config-wrapper"\>  
    <Field\>  
    <FieldLabel required htmlFor\="title"\>  
    {localeTexts.ConfigScreen.page.label}  
    </FieldLabel\>  
    <TextInput  
    type\="text"  
    required  
    value\={state?.installationData?.configuration?.title}  
    placeholder\={localeTexts.ConfigScreen.page.placeholder}  
    name\="title"  
    onChange\={updateConfig}  
    /\>  
    </Field\>  
    </Form\>
    

*   Add the below function inside the component:
    
*   const updateConfig \= async (e: any) \=> {  
    // eslint-disable-next-line prefer-const  
    let { name: fieldName, value: fieldValue } \= e?.target || {};  
    if (typeof fieldValue \=== "string") fieldValue \= fieldValue?.trim();  
    const updatedConfig \= state?.installationData?.configuration || {};  
    updatedConfig\[fieldName\] \= fieldValue;  
       
    if (typeof state?.setInstallationData !\== "undefined") {  
    await state?.setInstallationData({  
    ...state.installationData,  
    configuration: updatedConfig,  
    });  
    }  
       
    return true;  
    };
    
*   Save your file and make sure that your application is still running with no errors.  
    
*   **Tip**: in case you might have experienced any issues or your application is erroring, the final code is provided in the **src/containers/ConfigScreen/solution.txt** file, which contents you can copy and paste into the **src/containers/ConfigScreen/AppConfiguration.tsx** file.
    
*   You should see the following Configuration screen in your browser:
    
*   ![BuildingMarketApps\_L6\_img-1.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt65ba5d2c51370f0a/67ddd47a20a3dc1de3f82d1c/BuildingMarketApps_L6_img-1.png)
    
*   From this moment on, as soon as the value in the input field changes, it gets saved. If you reload the page you will see that the value is persisted.
    

This exercise just show you how to to save and read configuration from Consesntack for your marketplace applications. As a developer, you can create your own interface with differnt UI elements and as many inputs as needed to store as many configuration parameters as needed, too. These configuration parameters are then available in the rest of the locations, shall you need to use them in other places.

**Tip**: make sure you go over the entire code in the file to get a better understanding on how the logic access the configuration.

If you want to learn more about this location, please visit our documentation: [App Configuration Location](https://www.contentstack.com/docs/developers/developer-hub/app-config-location)

In the next module you will create a Custom Field.

#### Key takeaways

- Connect **Implementing App Locations: App Configuration** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 07 — Implementing App Locations: Custom Field

<!-- ai_metadata: {"lesson_id":"07","type":"text","duration_minutes":1,"topics":["Implementing","App","Locations","Custom","Field"]} -->

#### Lesson text

# **Implementing App Locations: Custom Field**

The Custom Field Location of an app lets you add/ create custom fields that you can use in your content type.

Apart from using the default fields such as "Single-line textbox", "Rich Text Editor", and so on, you can integrate with numerous business applications, such as "Bynder", "Cloudinary", "Shopify", by adding them as custom fields to your stack's content type.

Contentstack loads this location of your app inside an iframe in the entry editing page. You can use the app sdk framework to access the entry data, manipulated it, and store your custom filed data into it. This will allow you to store field values in the entry from third-party systems such as Digital Asset Management systems Product Information Management systems, etc...

## **Use Case**

One of the most common use cases in Contenstack to leverage custom fields, is typically when you want to manage assets from a different system and then bring the asset id and/or reference into Contenstack, so your editors can browse the Digital Asset Management system and choose their images from there and associate them with other content managed in Contenstack.

Similarly you can apply the same concept if you need to browse product categories or attributes from your Product Information Management system so, again, your editor can choose the right product discounts, product ids, product features, etc... when, for instance creating product promotional pages. 

In order to read and save this values as a field as part of the entry you can refer to the following code snippet:

import ContentstackAppSdk from "@contentstack/app-sdk";  
   
const \[state, setState\] \= useState<TypeAppSdkConfigState\>({  
config: {},  
entry: {},  
location: {},  
appSdkInitialized: false,  
});  
useEffect(() \=> {  
ContentstackAppSdk.init()  
.then(async (appSdk) \=> {  
const config \= await appSdk?.getConfig();  
const entryData \= appSdk.location.CustomField?.field?.getData();  
appSdk.location.CustomField?.frame.enableAutoResizing();  
setState({  
config,  
entry: entryData,  
location: appSdk?.location,  
appSdkInitialized: true,  
});  
})  
.catch((error) \=> {  
console.error("appSdk initialization error", error);  
});  
}, \[\]);  
 

Using the config and the entry objects in the state variable, you can now render your UI of the custom field location as per your requirement. If you need to save/overwrite the updated data to the custom field of an entry, then you have to use the **state.location.CustomField.field.setData()** function as shown below:

state.location.CustomField?.field?.setData(updatedDataThatHasToBeStoredInCustomFieldOfTheEntry);

## **Exercise 2**

In this exercise you will add a button to your custom field, an input box and a modal. When the exercise is completed, you will be able to click on the button, open the dialog, choose a product id from it (hardcoded in this example, but you could easily implement the logic to retrieve the actual product id from your third party system), and save it as part of the entry data.

*   Using your code editor, access the file located at: **src/containers/CustomField/CustomField.tsx**
    
*   Import the following react components from the Contenstack venus components library:
    
*   import { Tag, cbModal } from "@contentstack/venus-components";
    
*   Create the following two new functions inside the component. This code will display a modal dialog upon clicking on a button:
    
*   const productModal \= (props: any) \=> (  
    <ProductModal  
    updateSelectedItems\={updateSelectedItems}  
    // eslint-disable-next-line react/jsx-props-no-spreading  
    {...props}  
    /\>  
    );  
       
    const handleClick \= () \=> {  
    cbModal({  
    component: productModal,  
    modalProps: {  
    onClose: () \=> {},  
    onOpen: () \=> {},  
    size: "xsmall",  
    },  
    });  
    };  
     
    
*   Add the below code in  the **renderCustomFile** function to replace the placeholder that  says “{//add tag code here}”.
    
*   <Tag  
    version\="v1"  
    tags\={selectedItems}  
    onChange\={(tags: any) \=> setSelectedItems(tags)}  
    /\>  
     
    
*   Next, create a new file named **ProductModal.tsx** in the following folder: **src/containers/CustomField/**
    
*   Paste the following code into the file:
    
*   import {  
    Button,  
    ButtonGroup,  
    Field,  
    FieldLabel,  
    ModalBody,  
    ModalFooter,  
    ModalHeader,  
    TextInput,  
    } from "@contentstack/venus-components";  
    import React, { useState } from "react";  
       
    import localeTexts from "../../common/locales/en-us";  
       
    const ProductModal \= ({ updateSelectedItems, ...props }: any) \=> {  
    const \[name, setName\] \= useState("");  
       
    return (  
    <>  
    <ModalHeader  
    title\={localeTexts.CustomField.productModal.header}  
    closeModal\={props?.closeModal}  
    /\>  
    <ModalBody className\="audienceModalBody"\>  
    <Field\>  
    <FieldLabel required htmlFor\="name"\>  
    {"Name"}  
    </FieldLabel\>  
    <TextInput  
    type\="text"  
    name\="name"  
    placeholder\={localeTexts.CustomField.productModal.placeholder}  
    onChange\={(e: any) \=> setName(e.target.value)}  
    /\>  
    </Field\>  
    </ModalBody\>  
    <ModalFooter\>  
    <ButtonGroup\>  
    <Button buttonType\="light" onClick\={props?.closeModal}\>  
    {localeTexts.CustomField.productModal.cancelButton}  
    </Button\>  
    <Button  
    buttonType\="secondary"  
    icon\="AddPlusBold"  
    onClick\={() \=> {  
    updateSelectedItems(name);  
    props?.closeModal();  
    }}  
    \>  
    {localeTexts.CustomField.productModal.confirmButton}  
    </Button\>  
    </ButtonGroup\>  
    </ModalFooter\>  
    </\>  
    );  
    };  
       
    export default React.memo(ProductModal);  
     
    
*   Next, import this file into the **CustomField.tsx** component. Add this import to the **src/containers/CustomField/CustomField.tsx** file:
    
*   import ProductModal from "./ProductModal";
    
*   In that same field, make sure you associate the _handleClick_ function with the button _onClick_ property as follows:
    
*   <Tag  
    version\="v1"  
    tags\={selectedItems}  
    onChange\={(tags: any) \=> setSelectedItems(tags)}  
    /\>
    
*   Save your file and make sure that your application is still running with no errors.  
    
*   **Tip**: in case you might have experienced any issues or your application is erroring, the final code is provided in the **src/containers/CustomField/solution-1.txt** file, which contents you can copy and paste into the **src/containers/CustomField/CustomField.tsx** file.  
    Likewise, you can replace the contents of **src/containers/CustomField/ProductModal.tsx** with the solution provided in the **src/containers/CustomField/solution-2.txt** file.
    
*   At this point your custom field is ready. In order to visualize it, you will need to create a content type or modify an existing one to use the Custom Field. You can read more about how to manage custom fields in our [documentation portal](https://www.contentstack.com/docs/developers/create-custom-fields/about-custom-fields), more specifically the [Use Custom Field in Content Types](https://www.contentstack.com/docs/developers/create-custom-fields/use-custom-field-in-content-types) section.
    
*   These screenshots illustrate the process of creating a content type with a custom field:
    
    *   ![BuildingMarketApps\_L7\_img-1.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt3bdd6e185d07d7cd/67ddd4d120a3dc8816f82d21/BuildingMarketApps_L7_img-1.png)
        
    *   ![BuildingMarketApps\_L7\_img-2.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt2801f429b2fb846d/67ddd4e84a89c3021ba308e8/BuildingMarketApps_L7_img-2.png)
        
    *   ![BuildingMarketApps\_L7\_img-3.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt504f1d0eace9effc/67ddd4fcb1a1f3084c3efdc9/BuildingMarketApps_L7_img-3.png)
        
    *   ![BuildingMarketApps\_L7\_img-4.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt8f85a7850c0adb36/67ddd510b1a1f303f33efdcf/BuildingMarketApps_L7_img-4.png)
        
    *   Once you have a content type that uses the custom field, go ahead an create an entry, you should see something like this:
        
    *   ![BuildingMarketApps\_L7\_img-5.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt72d974f5cacafa58/67ddd527314d6717471b7525/BuildingMarketApps_L7_img-5.png)
        
    
*   As you can see the "Custom" filed displays. Go ahead and click the **+Add** button. You will see the dialog popping up. This example provides an input field for you to enter a value. In a real-world scenario, you can display here available attributes from your Product Information Management system, and save those in the entry. 
    
*   ![BuildingMarketApps\_L7\_img-6.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltbf2ccc07a99ab0b1/67ddd539db243fef1c12c82d/BuildingMarketApps_L7_img-6.png)
    
*   Next click Save to save the entry data. The value you entered will be persisted as part of the entry data and the next time you open it, it will be displayed in the custom field. 
    

**Tip**: make sure you go over the entire code in the file to get a better understanding on how the logic is implemented.

If you want to learn more about this location, please visit our documentation: [Custom Field Location](https://www.contentstack.com/docs/developers/developer-hub/custom-field-location)

In the next module you will create a Stack Dashboard Widget.

#### Key takeaways

- Connect **Implementing App Locations: Custom Field** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 08 — Implementing App Locations: Entry Sidebar

<!-- ai_metadata: {"lesson_id":"08","type":"text","duration_minutes":1,"topics":["Implementing","App","Locations","Entry","Sidebar"]} -->

#### Lesson text

The Sidebar Location provides the ability to integrate functionalities into your stack to analyze your entry content and recommend ideas.

This Sidebar location allow users to provide additional capabilities over content, thus optimizing the content to suit their requirements. Examples of such sidebar locations are SEO tag recommendations, sentiment analysis, language translation, and so on.

The Sidebar Widget location is located in the sidebar of the entry editorial page. In that location, the app sdk framework exposes the entry data as well as the application configuration data. This data then can be used by the user to, for example, display detailed information about the entry, or any other system you might e integrating with. 

## **Use Case**

One of the most common use cases in Contenstack to leverage the entry sidebar location, is typically when you want to integrate with third party translation services. In the entry sidebar you can provide your editor with options to send an entry for translation into any given locale, for example, which will initiate a translation workflow for instance. You an even send the data for automatic and/or machine translation and get the translated entry back, and persist it in your entry, again, in any given locale. This is just an example, but the possibilities here are endless, as you can pretty much build your custom UI in the sidebar for any business requirement you might be needing to fulfill. For example,  you can use Sidebar Widget UI location to display the complete details of a product selected from a third party e-commerce platform that has been added  using the custom field of an entry (like the one you developed on the previous module).

In order to read the configuration values and the entry data you can use the following code snippet:

import ContentstackAppSdk from "@contentstack/app-sdk";  
   
const \[state, setState\] \= useState<TypeAppSdkConfigState\>({  
config: {},  
entryData: {},  
location: {},  
appSdkInitialized: false,  
});  
useEffect(() \=> {  
ContentstackAppSdk.init()  
.then(async (appSdk) \=> {  
const config \= await appSdk?.getConfig();  
const entryData \= appSdk.location.SidebarWidget?.entry?.getData();  
setEntryData(data);  
setState({  
config,  
entryData,  
location: appSdk.location,  
appSdkInitialized: true,  
});  
})  
.catch((error) \=> {  
console.error("appSdk initialization error", error);  
});  
}, \[\]);  
 

## **Exercise 3**

In this exercise you will display certain data from the entry in the sidebar, an input box and a modal. When the exercise is completed, you will be able to click on the button, open the dialog, choose a product id from it (hardcoded in this example, but you could easily implement the logic to retrieve the actual product id from your third party system), and save it as part of the entry data.

*   Using your code editor, access the file located at: **src/containers/SidebarWidget/EntrySidebar.tsx**
    
*   In that file, locate the **renderProduct** function.
    
*   In that function replace the **return** statement with the following code snippet
    
*   return (  
    <ol\>  
    {products?.map((item: string, index: number) \=> (  
    <li key\={index} className\="list"\>{\`${index + 1}. ${item}\`}</li\>  
    ))}  
    </ol\>  
    );
    
*   Save your file and make sure that your application is still running with no errors.  
    
*   **Tip**: in case you might have experienced any issues or your application is erroring, the final code is provided in the **src/containers/SidebarWidget/solution.txt** file, which contents you can copy and paste into the **src/containers/SidebarWidget/EntrySidebar.tsx** file.
    
*   Using the left navigation bar, select the Entries section.
    
*   Next, find the entry you saved when you developed the Custom Field Location.
    
*   From the editorial form screen, using the right sidebar, open the **Widgets** section, and select _Sample App_:
    
*   ![BuildingMarketApps\_L8\_img-1.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt4cd2348d25be477d/67ddd582f7eccc6e4b90ddb2/BuildingMarketApps_L8_img-1.png)
    
*   ![BuildingMarketApps\_L8\_img-2.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt764c0be74ab65029/67ddd596b1a1f3788a3efdd8/BuildingMarketApps_L8_img-2.png)
    
*   Once opened, you should see something like this: 
    
*   ![BuildingMarketApps\_L8\_img-3.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt3de3dd3f8ab894ad/67ddd5b32d0b9838c7fd0b18/BuildingMarketApps_L8_img-3.png)
    

As you can see, the entry sidebar is displaying the products you added using the Custom Field and also the data you stored in your configuration app. This is a basic example but should give you the foundation to create your own sidebar application with a more complex UI as needed and leverage the configuration and the data entry to communicate with other systems and/or the entry itself.

**Tip**: make sure you go over the entire code in the file to get a better understanding on how the logic access the configuration and the entry data.

If you want to learn more about this location, please visit our documentation: [Entry Sidebar Location](https://www.contentstack.com/docs/developers/developer-hub/sidebar-location)

Next, you will develop a Dashboard Widget.

#### Key takeaways

- Connect **Implementing App Locations: Entry Sidebar** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 09 — Implementing App Locations: Stack Dashboard

<!-- ai_metadata: {"lesson_id":"09","type":"text","duration_minutes":1,"topics":["Implementing","App","Locations","Stack","Dashboard"]} -->

#### Lesson text

The Dashboard Location is a type of location that lets you create widgets for your stack dashboard.  
Using this location, you can create several useful widgets.  
Consider a widget that does the following operations:

*   Shows real-time data of stack usage
    
*   Lists all the entries published recently
    
*   Allows you to add your "To-Dos" for the day or take notes. 
    

Contentstack loads this location of your app inside an iframe in the Dashboard of your Stack. Like in previous modules, you can use the app sdk framework to, for example,  access your configuration values, in case you need to display some third-party data such as Analytics data or Kanban Jira information, etc... Basically is a place for you to integrate any information and functionality that you want available to your editors when accessing their stacks.

## **Use Case**

One of the most common use cases in Contenstack to leverage stack dashboard widgets, is typically to display analytics information on your sites, workflow statuses within Contentstack or other third-party systems. Add custom buttons and functionality to allow editors to quickly perform repetitive tasks or automate processes on other systems, for example, search engines, invalidate cache, preview most used promotions, or even get a comprehensive list of the best performing content in their digital properties.

Similar to the other locations, you can use the app sdk in order to read the stored configuration values, user information and even stack details as follows:

useEffect(() \=> {  
ContentstackAppSdk.init()  
.then(async (appSdk: any) \=> {  
const config \= await appSdk.getConfig();  
const currentUser \= appSdk?.currentUser;  
const stack \= appSdk?.stack;  
setState({  
config,  
currentUser,  
stack,  
appSdkInitialized: true,  
});  
})  
.catch((err: any) \=> {  
console.error("Error while initializing app sdk:", err);  
});  
}, \[\]);

Using these objects in the state variable, you can now render your UI according to your use case. 

Using the e-commerce platform integration use case, you can use Stack Dashboard Widget UI location to display analytics of all the products against each entry, i.e. what entries use what products and how those products are performing. Those analytics could also include the updates made to the products and whether the particular updated product data is published to the site or not. Again, the possibilities are endless.

## **Exercise 4**

In this exercise you will get the list of content types in Contenstack and you will display the amount of entries created based on each of those content types. The idea here is to illustrate how you can leverage the sdk to access other modules of the system, in this case, content types.

*   Using your code editor, access the file located at: **src/containers/DashboardWidget/StackDashboard.tsx**
    
*   In that file, within the available react component, create a state variable for the entry count:
    
*   const \[entriesCounts, setEntriesCounts\] \= useState<any\>(\[\]);
    
*   While initializing the appSDK, use the following code snippet to retrieve all the content types in the stack:
    
*   const { content\_types } \=  
    (await appSdk?.location?.DashboardWidget?.stack?.getContentTypes()) || {};
    
*   Next, you will use the apSDK to query for all the entries for all the content types. Here is the code:
    
*   let entriesCountObj \= await Promise.all(  
    content\_types?.map(async (content\_type: any) \=> {  
    const { entries } \=  
    (await appSdk?.location?.DashboardWidget?.stack  
    ?.ContentType(content\_type.uid)  
    ?.Entry.Query()  
    .find()) || {};  
    return {  
    uid: content\_type?.uid,  
    title: content\_type?.title,  
    count: entries?.length,  
    };  
    })  
    );  
    setEntriesCounts(entriesCountObj);
    
*   Next, import the following component from the Venus Component Library by pasting int into the file:
    
*   import { InfiniteScrollTable } from "@contentstack/venus-components";
    
*   The **InfiniteScrollTable** component you just imported requires a columns object to use for its columns definition. Use the following code to define the table columns:
    
*   const columns \= \[  
    {  
    Header: "Title",  
    accessor: "title",  
    columnWidthMultiplier: 1.9,  
    },  
    {  
    Header: "UID",  
    accessor: "uid",  
    id: "uid",  
    columnWidthMultiplier: 1.4,  
    },  
    {  
    Header: "Count",  
    accessor: "count",  
    columnWidthMultiplier: 0.7,  
    },  
    \];
    
    *   To read more on how to use InfiniteScrollTable, refer to the [Venus Components Documentation](https://venus-storybook.contentstack.com/?path=/story/overview-getting-started--page)
        
    
*   To signal the loaded state of the rows in the Table, you need to populate the **itemStatusMap** object. To do that, you will:
    
    *   Initialize the **itemStatusMap**:
        
    *   let itemStatusMap: any \= {};  
         
        
    *   Then, set the state as ‘**loaded**’ when the **entriesCount** is set:
        
    *   useEffect(() \=> {  
        entriesCounts.forEach(  
        (\_: any, index: any) \=> (itemStatusMap\[index\] \= "loaded")  
        );  
        }, \[entriesCounts, itemStatusMap\]);
        
    *   And lastly, render the InifinteScrollTable in the DOM:
        
    *   <div className\="dashboard"\>  
        <div className\="dashboard-container"\>  
        {state?.appSdkInitialized && (  
        <>  
        <h2\>Entries Count</h2\>  
        <InfiniteScrollTable  
        data\={entriesCounts}  
        columns\={columns}  
        uniqueKey\={"uid"}  
        totalCounts\={entriesCounts.length}  
        fetchTableData\={() \=> {}}  
        loadMoreItems\={() \=> {}}  
        itemStatusMap\={itemStatusMap}  
        /\>  
        </\>  
        )}  
        </div\>  
        </div\>
        
    
*   Save your file and make sure that your application is still running with no errors.  
    
*   **Tip**: in case you might have experienced any issues or your application is erroring, the final code is provided in the **src/containers/DashboardWidget/solution.txt** file, which contents you can copy and paste into the **src/containers/DashboardWidget/StackDashboard.tsx** file.
    
*   Now, go back to your Stack Dashboard. You can do so, by clicking the top \`Dashboard\` icon in the left navigation bar.
    
*   ![BuildingMarketApps\_L9\_img-1.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt042ebb93137ccbdc/67ddd6065e486d10db71bba7/BuildingMarketApps_L9_img-1.png)
    
*   You will see your Stack Dashboard Widget. It should look similar to this: 
    
*   ![BuildingMarketApps\_L9\_img-2.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt8051783e925f5d8d/67ddd61ac566ee763c5e05d7/BuildingMarketApps_L9_img-2.png)
    

**Tip**: make sure you go over the entire code in the file to get a better understanding on how the logic is implemented.

If you want to learn more about this location, please visit our documentation: [Stack Dashboard Location](https://www.contentstack.com/docs/developers/developer-hub/dashboard-location)

In the next module you will implement the Asset Sidebar Widget location.

## **Video: Implementing Stack Dashboard Location**

The following video walks you through the previous exercise steps so you can review and follow along:

#### Key takeaways

- Connect **Implementing App Locations: Stack Dashboard** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 10 — Implementing App Locations: Asset Sidebar

<!-- ai_metadata: {"lesson_id":"10","type":"text","duration_minutes":1,"topics":["Implementing","App","Locations","Asset","Sidebar"]} -->

#### Lesson text

The Asset Sidebar Location lets you create customized sidebar widgets that extend the functionality of your assets and enhance their editorial experience to suit your needs.  
You can efficiently manage, transform, and optimize the assets in your stack.

The Asset Sidebar Widget location is located in the sidebar of the asset editorial page. In that location, the app sdk framework exposes the asset data as well as the application configuration data. This data then can be used by the user to, for example, add additional metadata to the asset or displaying additional information about the asset that might not be present by default in the detail view. 

## **Use Case**

One of the most common use cases in Contenstack to leverage the entry sidebar location, is typically when you want to add additional metadata associated to the asset. In the asset sidebar you can provide your editor with additional fields to manage extra information about the asset, for example, SEO related metadata, accessibility information, or even different presets or variations for the asset that can be tailored, for example, to the size of the screen and/or the capabilities of the display the asset will be presented on. A good example of an existing asset sidebar extension managed and maintained by Contenstack is the [Image Preset Builder](https://www.contentstack.com/marketplace/image-preset-builder).

In order to read the configuration values and the asset data you can use the following code snippet:

useEffect(() \=> {  
ContentstackAppSdk.init()  
.then(async (appSdk) \=> {  
const config \= await appSdk.getConfig();  
const asset \= appSdk?.location?.AssetSidebarWidget?.currentAsset;  
await setState({  
config,  
asset,  
appSdkInitialized: true,  
});  
})  
.catch((sdkError: any) \=> {  
console.error("Error while initializing app sdk: ", sdkError);  
});  
}, \[\]);

## **Exercise 5**

In this exercise you will display the data from the asset in the sidebar, in a field-value pair fashion. 

Using your code editor, access the file located at: **src/containers/AssetSidebarWidget/AssetSidebar.tsx**

*   In that file, inside the _AssetSidebarExtension_, add the following code snippet to create a state variable to store the asset data:
    
*   const \[assetData, setAssetData\] \= useState<any\>({});
    
*   Inside the _useEffect_ code, and within the _appSdk.init()_ **,** add the following code snippet to use the appSdk object to set the data in your local state:
    
*   const assetDataFromLocation \=  
    await appSdk?.location?.AssetSidebarWidget?.getData();  
    setAssetData(assetDataFromLocation);
    
*   Next, you add the following JSX snippet in that same file. Look for this placeholder: **{/\* your code goes here \*/}**, and replace it with this code:
    
*   <div\>Filename : {assetData?.filename}</div\>  
    <div\>  
    Dimensions : {assetData?.dimension?.height} x {assetData?.dimension?.width}  
    </div\>  
    <div\>URL: {assetData?.url}</div\>
    
*   Save your file and make sure that your application is still running with no errors.  
    
*   **Tip**: in case you might have experienced any issues or your application is erroring, the final code is provided in the **src/containers/AssetSidebarWidget/solution.txt** file, which contents you can copy and paste into the **src/containers/AssetSidebarWidget/AssetSidebar.tsx** file.
    
*   Using the left navigation bar, select the Assets section.
    
*   Next, find an asset and click on it. This will display the asset details. This will work on any asset.
    
*   ![BuildingMarketApps\_L10\_img-4.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blted35390bdd69fe1a/67ddd6c2a714582179b9bae8/BuildingMarketApps_L10_img-4.png)
    
*   From the editorial form screen, using the right sidebar, open the **Widgets** section, and select _Sample App_:
    
*   ![BuildingMarketApps\_L10\_img-1.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt35b158833dab0fd8/67ddd65fa6d88e507e053a3a/BuildingMarketApps_L10_img-1.png)
    
*   ![BuildingMarketApps\_L10\_img-2.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt7a0fd085c01578dd/67ddd677cb0241239641bb1e/BuildingMarketApps_L10_img-2.png)
    
*   Once opened, you should see something like this: 
    
*   ![BuildingMarketApps\_L10\_img-3.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltb24e5863574c6103/67ddd68c17f7b4e3e20d0698/BuildingMarketApps_L10_img-3.png)
    

As you can see, the asset sidebar is displaying some attributes from the asset. This is a basic example but should give you the foundation to create your own asset sidebar application with a more complex UI as needed and leverage the configuration and the asset data to communicate with other systems and/or the asset itself.

**Tip**: make sure you go over the entire code in the file to get a better understanding on how the logic access the configuration and the asset data.

If you want to learn more about this location, please visit our documentation: [Asset Sidebar Location](https://www.contentstack.com/docs/developers/developer-hub/asset-sidebar-location)

Next, you will develop a Full Page location extension.

#### Key takeaways

- Connect **Implementing App Locations: Asset Sidebar** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 11 — Implementing App Locations: Full Page

<!-- ai_metadata: {"lesson_id":"11","type":"text","duration_minutes":1,"topics":["Implementing","App","Locations","Full","Page"]} -->

#### Lesson text

The Full Page location gives you full screen real state in Contenstack to integrate your applications. This will display on the left main navigation bar with the icon you chose when registering the location in Developer Hub:

![BuildingMarketApps\_L11\_img-1.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt93aed1b21138c5c7/67ddd703460eb4e71e947d6b/BuildingMarketApps_L11_img-1.png)

In that location, the app sdk framework exposes the application configuration data, as well as other objects such as the stack, user, etc... similar to the Stack Dashboard Widget location. 

When the user clicks on the icon on the left navigation bar, the app will get loaded inside an iframe in the main area within the dashboard. 

## **Use Case**

One of the most common use cases in Contenstack to leverage the full page location, is typically when you want to add additional functionality or a custom application that requires a larger space to be used. With this extension location you have endless possibilities, and you can even bring your own custom app that might not necessarily interact with Contenstack, but that makes sense to have integrated so your editors go to the same place to do all their work. For example, you can implement access to a network location to access certain files and documentation; you can provide a dashboard with useful information and/or useful resources so your editors can access those at a glance; etc. A good example of an existing full page sidebar extension managed and maintained by Contenstack is the [Release Preview](https://www.contentstack.com/docs/developers/marketplace-apps/release-preview) marketplace app.

In order to read the configuration values and the get access to the available objects in this location, you can use the following code snippet:

useEffect(() \=> {  
ContentstackAppSDK.init().then(async (appSdk) \=> {  
const config \= await appSdk?.getConfig();  
setState({  
config,  
location: appSdk?.location,  
appSdkInitialized: true,  
});  
});  
}, \[\]);

## **Exercise 6**

In this exercise you will display display a table with all the entries for one specific content type. You will be able to select the content type using an dropdown, and upon selection of the content type, the table will update its data with the list of entries based on the selected content type. 

Using your code editor, access the file located at: **src/containers/FullPage/FullPage.tsx**

*   In order to use the **Select** venus component, import it from the venus component library adding this piece of code at the tope of the file:
    
*   import { Select } from "@contentstack/venus-components";
    
*   Now, inside the **FullPageExtension** component, add the following code snippet to create a state variable to store the available content types:
    
*   const \[contentTypes, setContentTypes\] \= useState<any\[\]\>();
    
*   Inside the _useEffect_ code, and within the _appSdk.init()_ **,** add the following code snippet to get all the available content types from the stack object and to store them in the previously defined state:
    
*   const { content\_types } \=  
    (await appSdk?.location?.FullPage?.stack?.getContentTypes()) || {};  
    setContentTypes(content\_types);
    
*   Next, you will add a Dropdown component to contain the different content types to use in the UI by the users. For this, you first need to create a local state to store the options and the selected Content Type. Right after the previous snipped code, paste this code, too:
    
*   const \[contentTypeOptions, setContentTypeOptions\] \= useState<any\[\]\>(\[\]);  
    const \[selectedContentType, setSelectedContentType\] \= useState<any\>("");
    
*   Additionally, you will need to add the following code snippet, after the existing **useEffect** hook:
    
*   useEffect(() \=> {  
    if (!state.appSdkInitialized) return;  
    let dropdownOptions: any \= \[\];  
    contentTypes?.forEach((contentType: any) \=> {  
    dropdownOptions.push({  
    label: contentType?.title,  
    value: contentType?.uid,  
    });  
    });  
    setContentTypeOptions(dropdownOptions);  
    }, \[contentTypes\]);
    
*   Next, you will need a listener to update the selected Content Type whenever the user selects a different value. Add  the following code snippet right after the code you just pasted:
    
*   const handleDropdownChange \= (e: any) \=> {  
    setSelectedContentType(e);  
    };
    
*   Next, you will add the UI Select dropdown element.  Replace the **{/\* your code goes here \*/}** placeholder with the following code:
    
*   <Select  
    options\={contentTypeOptions}  
    value\={selectedContentType}  
    onChange\={handleDropdownChange}  
    placeholder\="Select Content Type"  
    selectLabel\="Content Type"  
    /\>;
    
*   Now that we are done with the data retrieval and UI selection of content types, let's work on the display of the entries associated with each content type. For that you will need:
    
    *   A state variable to store the current list of entries
        
    *   A function that will retrieve the entries for the selected content type
        
    *   A _useEffect_ hook that will run every time the content type selection changes, and that will update the table accordingly.
        
    
*   Copy the following code inside your **FullPageExtension** right before the **return** statement:
    
*   const \[entriesData, setEntriesData\] \= useState<any\[\]\>(\[\]);  
       
    const getEntriesOfAContentType \= async (contentTypeUid: any) \=> {  
    const { entries } \=  
    (await state?.location?.FullPage?.stack  
    ?.ContentType(contentTypeUid)  
    .Entry.Query()  
    .find()) || {};  
    setEntriesData(entries);  
    };  
       
    useEffect(() \=> {  
    if (!Object.keys(selectedContentType).length) return;  
    getEntriesOfAContentType(selectedContentType?.value);  
    }, \[selectedContentType\]);
    
*   And lastly, let's add the UI component and code to display the table of entries. We will be using the _InfiniteScrollTable_  component like we did previously in a past module. Let's import that component adding this clause at the top of the file:
    
*   import { InfiniteScrollTable } from "@contentstack/venus-components";
    
*   Now, right before the **return** statement, paste this snippet to define the columns of our table:
    
*   const columns \= \[  
    {  
    Header: "Title",  
    accessor: "title",  
    columnWidthMultiplier: 1.9,  
    },  
    {  
    Header: "UID",  
    accessor: "uid",  
    id: "uid",  
    columnWidthMultiplier: 1.5,  
    },  
    {  
    Header: "Locale",  
    accessor: "locale",  
    columnWidthMultiplier: 1,  
    },  
    {  
    Header: "Updated At",  
    accessor: (obj: any) \=> {  
    return new Date(obj?.updated\_at).toUTCString();  
    },  
    columnWidthMultiplier: 2,  
    },  
    \];  
     
    
*   **Note**: we have converted the date into a UTC string to display in the table. To learn more about this component, please visit our Venus Component Library [documentation](https://venus-storybook.contentstack.com/?path=/story/components-table--default).
    
*   Let's keep adding some more code. To signal the loaded state of the rows in the Table, you need to maintain a status map object. Copy this code snipped right after the **columns** variable you just defined:
    
*   let itemStatusMap: any \= {};  
    useEffect(() \=> {  
    entriesData?.forEach(  
    (\_: any, index: any) \=> (itemStatusMap\[index\] \= "loaded")  
    );  
    }, \[entriesData\]);
    
*   And lastly, let's add the table UI elements. Copy this JSX snippet right after the **Select** JSX component you pasted earlier:
    
*   <InfiniteScrollTable  
    data\={entriesData}  
    columns\={columns}  
    uniqueKey\={"uid"}  
    totalCounts\={entriesData.length}  
    fetchTableData\={() \=> {}}  
    loadMoreItems\={() \=> {}}  
    itemStatusMap\={itemStatusMap}  
    /\>;
    
*   Save your file and make sure that your application is still running with no errors.  
    
*   **Tip**: in case you might have experienced any issues or your application is erroring, the final code is provided in the **src/containers/FullPage/solution.txt** file, which contents you can copy and paste into the **src/containers/FullPage/FullPage.tsx** file.
    
*   Let's take a look at the Full Page extension. From the Stack dashboard screen, access the Full Page location by clicking on the _Sample App_ icon:
    
*   ![BuildingMarketApps\_L11\_img-2.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltb624c265797a4908/67ddd721f7ecccef8390ddcc/BuildingMarketApps_L11_img-2.png)
    
*   You should see something like this:
    
*   ![BuildingMarketApps\_L11\_img-3.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt7fd8bfdb21f67065/67ddd73a3a0f38bf6800f08b/BuildingMarketApps_L11_img-3.png)
    
*   Then select a content type form the dropdown and the table should update accordingly
    
*   ![BuildingMarketApps\_L11\_img-4.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltba037dbc6447399b/67ddd74e314d6730271b7543/BuildingMarketApps_L11_img-4.png)
    

As you can see, the full page shows the dropdown with the available content types in the stack, and upon selection, a table with the list of entries based on such content type is also displayed. This is a basic example but should give you the foundation to create your own full page application with a more complex UI as needed.

**Tip**: make sure you go over the entire code in the file to get a better understanding on how the logic access the configuration and the asset data.

If you want to learn more about this location, please visit our documentation: [Full Page Location](https://www.contentstack.com/docs/developers/developer-hub/full-page-location)

Next, you will develop a Field Modifier location extension.

## **Video: Implementing Full Page Location**

The following video walks you through the previous exercise steps so you can review and follow along:

#### Key takeaways

- Connect **Implementing App Locations: Full Page** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 12 — Implementing App Locations: Field Modifier

<!-- ai_metadata: {"lesson_id":"12","type":"text","duration_minutes":1,"topics":["Implementing","App","Locations","Field","Modifier"]} -->

#### Lesson text

The Field Modifier Location is a type of UI location which extends the capabilities of entry fields. With the Field Modifier UI location, you can allow the different apps to appear on defined field data types such as Text, Number, JSON, Boolean, File, Reference fields etc...

## **Use Case**

Using the Field Modifier UI location you can manage the content of a field, you can get the value of a field, modify it according to your use case,  and update it in the entry. Form this location, liken in the others, you can get the application configuration, and entry data using Contentstack SDK, too.

A common use case can be machine translation of a field, you can send a single field for translation to a third-party system, wait for it response and update the field. You can also use this location to implement logic to get sentiment analysis, ensure that your editors follow your companies messaging guidelines by removing unwanted terms and ensuring that certain others are used etc...

You can refer to the sample code below on how to access the field value data and to update the entry as needed:

useEffect(() => {
  ContentstackAppSdk.init()
    .then(async (appSdk: any) => {
      const config = await appSdk?.getConfig();
      const entryData =
        appSdk?.location?.FieldModifierLocation?.field?.getData() || "";
      appSdk?.location?.CustomField?.frame?.enableAutoResizing();

      setEntry(entryData);

      setState({
        config,
        location: appSdk?.location,
        appSdkInitialized: true,
      });
      setLoading(false);
    })
    .catch((error: any) => {
      console.error("appSdk initialization error", error);
      setError("Something went wrong!");
    });
}, \[\]);

const updateEntry = (updatedData: any) => {
  const { location } = state;
  location.FieldModifierLocation?.field?.setData(updatedData);
};

## **Exercise 7**

In this exercise you will show the configuration value stored when using the App Configuration location, and also you will add a button, that when clicked, will capitalize the value of the field.

*   Using your code editor, access the file located at: **src/containers/FieldModifier/FieldModifier.tsx**
    
*   In that file, locate the **{/\* your code goes here \*/}** and replace it with this: 
    

<>

<h3>{state?.config?.title}</h3>

<button onClick={() => updateEntry(entry.toUpperCase())}>Capitalize</button>

</>

**Note**: notice how in this case we used a regular _button_ html element as opposed to a _Button_ component from the Venus Component Library. This shows how you can use the library of your choice and any components that might fit better your application and your UI requirements.

Save your file and make sure that your application is still running with no errors.  

**Tip**: in case you might have experienced any issues or your application is erroring, the final code is provided in the **src/containers/SidebarWidget/solution.txt**file, which contents you can copy and paste into the **src/containers/SidebarWidget/EntrySidebar.tsx** file.

Using the left navigation bar, select the Entries section.

Next, find the entry you saved when you developed the Custom Field Location.

From the editorial form screen, over the _title_ field click the _Sample App_ icon:

![BuildingMarketApps\_L12\_img-1.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt2f6625927a6db60e/67ddd9c6986725010d888f6f/BuildingMarketApps_L12_img-1.png)

You should see something like this:

![BuildingMarketApps\_L12\_img-2.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltf05e3c45b26df693/67ddd9f0632b93877cd4a792/BuildingMarketApps_L12_img-2.png)

On the field modifier dialog, click the Capitalize button, it should capitalize the value of the field as follows:

![BuildingMarketApps\_L12\_img-3.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltcfc1c7bf4056b920/67ddda2b443bd6f690f1d0af/BuildingMarketApps_L12_img-3.png)

As you can see the title has been capitalized.

**Tip**: make sure you go over the entire code in the file to get a better understanding on how the logic is implemented.

If you want to learn more about this location, please visit our documentation: [Field Modifier Location](https://www.contentstack.com/docs/developers/developer-hub/field-modifier-location)

Next, you will develop a JSON RTE extension.

## **Video: Implementing Field Modifier Location**

The following video walks you through the previous exercise steps so you can review and follow along:

#### Key takeaways

- Connect **Implementing App Locations: Field Modifier** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 13 — Implementing App Locations: JSON RTE

<!-- ai_metadata: {"lesson_id":"13","type":"text","duration_minutes":1,"topics":["Implementing","App","Locations","JSON","RTE"]} -->

#### Lesson text

The JSON Rich Text Editor Plugins lets you add/create custom plugins to extend the functionality of your JSON Rich Text Editor as per your needs. You can use third-party applications to interact with your JSON Rich Text Editor content.

## **Use Case**

With prebuilt plugins such as Highlight, Info Panel, and Word Count, you can enhance the JSON Rich Text Editor (JSON RTE) field. Instead of using separate custom fields to interact with the content within the JSON RTE, create and add plugins to your JSON RTE for real-time adaptiveness.

## **Exercise 8**

For this exercise you will need to navigate to a different location in the project directory to work on the JSON RTE plugin code. Once you complete the exercise a compiled file with the plugin will be placed in the _public_ folder of the main project and will be served as defined in the JSON RTE location path definition. 

In this exercise you will create a plugin that will capitalize the text in the JSON RTE, similar to the logic implemented for the Field Modifier location.

*   First navigate into the **\[project-root\]/rte/src** folder, that's where the core of the work will be done for this exercise.
    
*   Next, in that folder, create a new file under the _rte/src_ folder, and name it **academy.tsx**
    
*   Copy the following code into the file contents:
    

import <span>{</span> cloneDeep <span>}</span> from <span>"lodash"</span><span>;</span>  
   
<span>export</span> <span>const</span> onClickHandler <span>=</span> <span>(</span>rte<span>:</span> any<span>)</span> <span>=&gt;</span> <span>{</span>  
<span>const</span> getNode <span>=</span> rte<span>?</span>.<span>getNode</span><span>(</span><span>\[</span><span>0</span><span>\]</span><span>)</span><span>;</span>  
  let rteData<span>:</span> any <span>=</span> <span>\[</span><span>\]</span><span>;</span>  
   
<span>const</span> findRteObj <span>=</span> <span>(</span>obj<span>:</span> any<span>)</span> <span>=&gt;</span> <span>{</span>  
<span>for</span> <span>(</span>let key in obj<span>)</span> <span>{</span>  
<span>if</span> <span>(</span>typeof obj<span>\[</span>key<span>\]</span> <span>===</span> <span>"object"</span> <span>&amp;&amp;</span> obj<span>\[</span>key<span>\]</span> <span>!</span><span>==</span> null<span>)</span> <span>{</span>  
<span>if</span> <span>(</span>obj<span>\[</span>key<span>\]</span><span>?</span>.<span>type</span> <span>===</span> <span>"p"</span><span>)</span> <span>{</span>  
          rteData.<span>push</span><span>(</span>obj<span>\[</span>key<span>\]</span><span>)</span><span>;</span>  
<span>}</span> <span>else</span> <span>{</span>  
          findRteObj<span>(</span>obj<span>\[</span>key<span>\]</span><span>)</span><span>;</span>  
<span>}</span>  
<span>}</span>  
<span>}</span>  
<span>}</span><span>;</span>  
   
  findRteObj<span>(</span>getNode<span>)</span><span>;</span>  
   
<span>const</span> path <span>=</span> rteData<span>?</span>.<span>map</span><span>(</span><span>(</span>path<span>:</span> any<span>)</span> <span>=&gt;</span> <span>{</span>  
<span>return</span> rte<span>?</span>.<span>getPath</span><span>(</span>path<span>)</span><span>;</span>  
<span>}</span><span>)</span><span>;</span>  
   
<span>const</span> rteDataCopy <span>=</span> cloneDeep<span>(</span>rteData<span>)</span><span>;</span>  
   
  rteDataCopy.<span>forEach</span><span>(</span><span>(</span>element<span>:</span> any<span>)</span> <span>=&gt;</span> <span>{</span>  
    element<span>?</span>.<span>children</span><span>?</span>.<span>forEach</span><span>(</span><span>(</span>j<span>:</span> any<span>)</span> <span>=&gt;</span> <span>{</span>  
      j.<span>text</span> <span>=</span> j<span>?</span>.<span>text</span><span>?</span>.<span>toUpperCase</span><span>(</span><span>)</span><span>;</span>  
<span>}</span><span>)</span><span>;</span>  
<span>}</span><span>)</span><span>;</span>  
   
<span>for</span> <span>(</span>let i <span>=</span> <span>0</span><span>;</span> i <span>&lt;</span> rteDataCopy<span>?</span>.<span>length</span><span>;</span> i<span>++</span><span>)</span> <span>{</span>  
    rte<span>?</span>.<span>deleteNode</span><span>(</span><span>{</span> at<span>:</span> path<span>\[</span>i<span>\]</span> <span>}</span><span>)</span><span>;</span>  
    rte.<span>insertNode</span><span>(</span>rteDataCopy<span>\[</span>i<span>\]</span>, <span>{</span>  
      at<span>:</span> path<span>\[</span>i<span>\]</span>,  
<span>}</span><span>)</span><span>;</span>  
<span>}</span>  
<span>}</span><span>;</span>  
 

*   Save your **academy.tsx** file and make sure that your application is still running with no errors.  
    
*   **Tip**: in case you might have experienced any issues or your application is erroring, the final code is provided in the **rte/src/solution-1.txt** file, which contents you can copy and paste into the **rte/src/academy.tsx** file.
    
*   Next navigate to the _rte/src/plugin.tsx_ and add the following two import statements:
    
*   import { Icon } from "@contentstack/venus-components";  
    import React from "react";  
    import { onClickHandler } from "./academy";
    
*   On that same file, replace the **return { }** statement with the following code snippet:
    

<span>if</span> <span>(</span><span>!</span>RTE<span>)</span> <span>return</span><span>;</span>  
   
<span>const</span> Academy <span>=</span> RTE<span>(</span><span>"academy"</span>, <span>(</span><span>)</span> <span>=&gt;</span> <span>{</span>  
<span>return</span> <span>{</span>  
    title<span>:</span> <span>"Capitalize"</span>,  
    icon<span>:</span> <span>&lt;</span>Icon icon<span>=</span><span>"Settings"</span> <span>/</span><span>&gt;</span>,  
<span>}</span><span>;</span>  
<span>}</span><span>)</span><span>;</span>  
   
<span>//@ts-ignore</span>  
Academy.<span>on</span><span>(</span><span>"exec"</span>, async <span>(</span>rte<span>:</span> RTE<span>)</span> <span>=&gt;</span> <span>{</span>  
<span>try</span> <span>{</span>  
    onClickHandler<span>(</span>rte<span>)</span><span>;</span>  
<span>}</span> <span>catch</span> <span>(</span>e<span>)</span> <span>{</span>  
    console.<span>error</span><span>(</span><span>"Error"</span>, e<span>)</span><span>;</span>  
<span>}</span>  
<span>}</span><span>)</span><span>;</span>  
   
<span>return</span> <span>{</span>  
  Academy,  
<span>}</span><span>;</span>  
 

*   Save your **plugin.tsx** file and make sure that your application is still running with no errors.
    
*   **Tip**: in case you might have experienced any issues or your application is erroring, the final code is provided in the **rte/src/solution-2.txt** file, which contents you can copy and paste into the **rte/src/plugin.tsx** file.
    
*   Now, for the plugin to be served you will need to build the code and deploy it to the _public_ folder of the main application. If you still have the boilerplate app running, stop it. You can do so, by pressing **CNTRL+C** in the terminal where the app is running.
    
*   Once the application has stopped you will need to execute the following commands from your terminal:
    
    Navigate to the root location of the rte plugin code: _\[project-root\]/rte/src:_
    
    cd \[project-root\]/rte/src
    
    First, install the dependencies by running the following command:
    
    npm install
    
    Second, build your plugin by running the following command:
    
    npm run build-academy
    
    Navigate back to the root of the main boilerplate app project **\[project-root\]/src**
    
    cd \[project-root\]/src
    
    Start the boilerplate app by running:
    
    npm start
    
    You should see something like this if you browse **http://localhost:3000/json-rte.js**
    
    ![BuildingMarketApps\_L13\_img-1.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt979797fe9bc4c1d2/67e19cad914c24e15e497198/BuildingMarketApps_L13_img-1.png)

Next, we will need to add a JSON RTE field to our Sample Content Type and activate the plugin so we can see it in action. To do that take the following steps:

*   Using the left navigation bar, click on the Content Models section
    
*   ![BuildingMarketApps\_L13\_img-2.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt51fdd596b74fba88/67e19cf8e5b23a6e6a9473b2/BuildingMarketApps_L13_img-2.png)
*   The select the **Sample Content Type** content type you created during the entry sidebar exercise. Alternatively you can use other Content Type, these steps work for any content type.
    
*   Add a JSON Rich Text Editor field to your content type
    
    ![BuildingMarketApps\_L13\_img-3.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blte173e1cba4a4c832/67e19d24d148ed8ae2a3fd72/BuildingMarketApps_L13_img-3.png)

*   Choose the _Sample App_ plugin
    
*   ![BuildingMarketApps\_L13\_img-4.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt0f30322a1f5db900/67e19f04b9f9a644badc4021/BuildingMarketApps_L13_img-4.png)
*   Save the content type and open an entry based on it. You can go to the Entries section as described on the previous Module where you created the entry sidebar location extension.
    

*   ![BuildingMarketApps\_L13\_img-5.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blteab33dc0720c7457/67e19f7f42bb03fbb96247ae/BuildingMarketApps_L13_img-5.png)

*   Once in the editor view of the entry, navigate to the JSON RTE field, type some content and select it. For example type in _Marketplace App Example_.
    
    ![BuildingMarketApps\_L13\_img-6.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt5d06c6668747a45b/67e19fbc8f508c4aad13970d/BuildingMarketApps_L13_img-6.png)
    
    Once you have some text selected, click on the toolbar "settings" icon. The text should be capitalized:
    
    ![BuildingMarketApps\_L13\_img-7.png](https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blte7e41ae577695ea8/67e1a040e6ff226fb40d14da/BuildingMarketApps_L13_img-7.png)
    

**Tip**: make sure you go over the entire code in the file to get a better understanding on how the logic is implemented.

If you want to learn more about JSON RTE plugins, please visit our documentation: [Create JSON RTE Plugins](https://www.contentstack.com/docs/developers/json-rich-text-editor-plugins/create-new-json-rte-plugin)

Congratulations! You just finished implementing the last UI Location supported by Marketplace Apps.

In the next module you will get access to some best practices and useful tips to develop your Marketplace Apps.

## **Video: Implementing JSON RTE Plugin**

The following video walks you through the previous exercise steps so you can review and follow along:

#### Key takeaways

- Connect **Implementing App Locations: JSON RTE** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 14 — Best Practices, Tips and Next Steps

<!-- ai_metadata: {"lesson_id":"14","type":"text","duration_minutes":1,"topics":["Best","Practices","Tips","and","Next","Steps"]} -->

#### Lesson text

Developers can follow these guidelines and simplify the application development process, its management, and its reviewing and approval process while building their applications.

1.  **Use Venus Components Library**: For building the UI, we strongly recommend to use Venus Components Library as it provides various UI components of contentstack. It makes the app’s UI consistent with Contenstack.
    
2.  **Define Project Structure**: While building a project, defining a scalable project structure is essential. The architecture and infra of the project depends on your app’s complexity. Define reusable components as smaller as possible to make it available in all the higher order of other components. You can use component-centric structure or any other structure of your choice. For larger UI projects, it's better to define themes for CSS and use it across the app.
    
3.  **Follow Naming Conventions**: Throughout the project, follow standard naming conventions. Developers can use the Pascal case to name components and Camel case to name the functions/ methods inside the components. Also for functions/methods used within a file locally can be prefixed with an underscore \_. For temporary variables that are used in loops or anonymous functions, snake case can be used.
    
4.  **Thorough E2E code testing**: Avoid potential errors by thorough automated test cases. To provide a quality experience to end users, a good end to end automated testing is suggested strongly. You can also use linters, pre-commit hooks to avoid syntax errors in code during the development phase itself.
    
5.  **Logging and Monitoring**: Writing logs helps to analyze various analytics and performance of the app. As a best practice, you can even use one of suitable logging libraries available. Its recommended to write detailed log events and avoid logging sensitive data/credentials.
    
6.  **App Security**:Apps should have the capability to encrypt data exchanged over the internet using HTTPS.Store client ID and client secret keys securely. We suggest you store them as environmental variables. It is mandatory for OAuth Apps to authenticate using an OAuth token.Also, it is highly recommended to use App signing feature for apps that have backend APIs to interact with the 3rd party systems. App signing is the feature/method through which the app can verify whether it is loaded inside contentstack only. When the app signing feature is enabled, Contentstack will attach an app-token in the source URL of the app iframe where it is loaded. Users can fetch the app-token, decode and verify it to confirm that the request is from Contentstack. This can prevent any other attackers from loading the app on their site. For verifying the app-token, you might need a public key. Below is the code snippet explaining the logic to verify the app-token from Contentstack.
    

const jwt \= require("jsonwebtoken");  
const appToken \= req.params\["app-token"\];  
const publicKey \= (  
  await axios("https://app.contentstack.com/.well-known/public-keys.json")  
).data\["signing-key"\];  
try {  
  const {  
    app\_uid,  
    installation\_uid,  
    organization\_uid,  
    user\_uid,  
    stack\_api\_key,  
  } \= jwt.verify(appToken, publicKey);  
  console.info("app token is valid!");  
} catch (e) {  
  console.error(  
    "app token is invalid or request is not initiated from Contentstack!"  
  );  
}

## **Next Steps**

You can look into [implementing OAuth with marketplace apps](https://www.contentstack.com/docs/developers/developer-hub/contentstack-oauth). If OAuth is enabled for an app, then contentstack will redirect to the OAuth endpoint upon installation by providing the access tokens and refresh tokens, which the apps can save in backend and use in future to communicate with Contentstack for authenticated actions.

## **Deploying and Hosting Your Application**

Once the app is developed, for building the app, you can execute npn run build command will result in creation of a build folder inside the project’s root directory. The contents of this build directory can be hosted in any of the static file hosting servers like AWS S3, for making the app publicly accessible on a hosted environment. Alternatively you can host your APP using **Contentstack Launch**, too. If you want to learn more about the Contenstack Launch service, please [visit our documentation](https://www.contentstack.com/docs/developers/automation-hub-connectors/launch).

#### Key takeaways

- Connect **Best Practices, Tips and Next Steps** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 15 — Summary

<!-- ai_metadata: {"lesson_id":"15","type":"text","duration_minutes":3,"topics":["Summary"]} -->

#### Lesson text

Through completing this course you have learned how to properly setup your development environment to work with the [marketplace boilerplate app](https://github.com/contentstack/marketplace-app-boilerplate). In addition, you implemented all the available extension locations in Contentstack's UI, namely

  

**Additional Resources:**

*   [Contentstack Documentation](https://www.contentstack.com/docs)
    
*   [Developer's Guide](https://www.contentstack.com/docs/developers)
    
*   [Marketplace App Boilerplate Installation Guide](https://www.contentstack.com/docs/developers/developer-hub/marketplace-app-boilerplate)
    
*   [Developer Hub](https://www.contentstack.com/docs/developers/developer-hub)
    
*   [Marketplace Applications](https://www.contentstack.com/docs/developers/marketplace-apps)
    
*   [Marketplace Platform Guides](https://www.contentstack.com/docs/developers/marketplace-platform-guides)
    
*   [Marketplace e-commerce App Boilerplate](https://www.contentstack.com/docs/developers/developer-hub/marketplace-ecommerce-app-boilerplate)

#### Key takeaways

- Connect **Summary** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

### Lesson 16 — Marketplace Apps Quiz

<!-- ai_metadata: {"lesson_id":"16","type":"text","duration_minutes":1,"topics":["LMS","Knowledge check"]} -->

#### Lesson text

**This lesson is a knowledge check hosted in the Academy LMS.** This companion Markdown contains **no quiz questions, answers, scoring rules, or explanations**.

#### Key takeaways

- Connect **Marketplace Apps Quiz** back to your stack configuration before moving to the next module.
- Capture one concrete artifact (screenshot, Postman call, or code snippet) that proves the step works in your environment.
- Re-read the delivery versus management boundary for anything you changed in the entry model.

## Resources & references

| Page | Companion Markdown |
| --- | --- |
| /courses/marketplace-apps/introduction-to-marketplace-apps-and-app-framework | /academy/md/courses/marketplace-apps/introduction-to-marketplace-apps-and-app-framework.md |
| /courses/marketplace-apps/marketplace-boilerplate | /academy/md/courses/marketplace-apps/marketplace-boilerplate.md |
| /courses/marketplace-apps/development-environment-setup-for-marketplace | /academy/md/courses/marketplace-apps/development-environment-setup-for-marketplace.md |
| /courses/marketplace-apps/creating-app-in-developer-hub-and-installing-the-app | /academy/md/courses/marketplace-apps/creating-app-in-developer-hub-and-installing-the-app.md |
| /courses/marketplace-apps/implementing-app-locations-introduction | /academy/md/courses/marketplace-apps/implementing-app-locations-introduction.md |
| /courses/marketplace-apps/implementing-app-locations-app-configuration | /academy/md/courses/marketplace-apps/implementing-app-locations-app-configuration.md |
| /courses/marketplace-apps/implementing-app-locations-custom-field | /academy/md/courses/marketplace-apps/implementing-app-locations-custom-field.md |
| /courses/marketplace-apps/implementing-app-locations-entry-sidebar | /academy/md/courses/marketplace-apps/implementing-app-locations-entry-sidebar.md |
| /courses/marketplace-apps/implementing-app-locations-stack-dashboard | /academy/md/courses/marketplace-apps/implementing-app-locations-stack-dashboard.md |
| /courses/marketplace-apps/implementing-app-locations-asset-sidebar | /academy/md/courses/marketplace-apps/implementing-app-locations-asset-sidebar.md |
| /courses/marketplace-apps/implementing-app-locations-full-page | /academy/md/courses/marketplace-apps/implementing-app-locations-full-page.md |
| /courses/marketplace-apps/implementing-app-locations-field-modifier | /academy/md/courses/marketplace-apps/implementing-app-locations-field-modifier.md |
| /courses/marketplace-apps/implementing-app-locations-json-rte | /academy/md/courses/marketplace-apps/implementing-app-locations-json-rte.md |
| /courses/marketplace-apps/best-practices-tips-and-next-steps | /academy/md/courses/marketplace-apps/best-practices-tips-and-next-steps.md |
| /courses/marketplace-apps/summary | /academy/md/courses/marketplace-apps/summary.md |
| /courses/marketplace-apps/marketplace-apps-quiz | /academy/md/courses/marketplace-apps/marketplace-apps-quiz.md |

## Supplement for indexing

### Content summary

Welcome to Introduction to Marketplace Applications Course! This course is intended to introduce developers to the process of creating new Marketplace Apps and installing them in Contenstack. Required for this course: 1.… Welcome to Introduction to Marketplace Applications Course! This course is intended to introduce developers to the process of creating new Marketplace Apps and installing them in Contenstack. Required for this course: 1. Account in Contentstack with access to an Organization as an Administrator or Developer. Create a training Org/Stack here (https://www.contentstack.com/academy/). Training stacks are available for 25 days. Alternatively, you can bootstrap a Starter App via the CLI (https://www.contentstack.com/docs/developers/cli/bootstrap-starter-apps) or from the Marketplace (https://app.con About the Course What this course is: A comprehensive guide for developers aiming to understand the intricacies of building marketplace apps. A deep dive into understanding the unique benefits of various extension locations, their specific use cases, common pitfalls, and best practices. A blend of general guidance and in-depth insights on recommended approach when building Contentstack extensions via marketplace apps. Not just any guide, but a set of step-by-step tutorials that walk the developer through the process of setting up a marketplace development environment using a boilerplate applic

### Retrieval tags

- marketplace
- marketplace-apps
- Introduction
- Apps
- and
- App
- Framework
- Boilerplate
- Development
- Environment
- Setup
- for
- Creating
- Developer

### Indexing notes

Chunk at each "### Lesson NN — Title" heading; copy lesson_id and topics from the preceding HTML comment into chunk metadata for RAG filters.
Course slug: marketplace-apps. Union of lesson topic tokens: Introduction, Marketplace, Apps, and, App, Framework, Boilerplate, Development, Environment, Setup, for, Creating, Developer, Hub, Installing, Implementing, Locations, Configuration, Custom, Field, Entry, Sidebar, Stack, Dashboard, Asset, Full, Page, Modifier, JSON, RTE, Best, Practices, Tips, Next, Steps, Summary, Quiz.
Do not embed or retrieve LMS-only quiz items or mastery exam answer keys from this export.

### Asset references

| Label | URL |
| --- | --- |
| BuildingMarketplaceApps\_L1\_img-1.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt9baf8e24ad9fac41/67ddd1beefd8a977054c06d5/BuildingMarketplaceApps_L1_img-1.png` |
| BuildingMarketplaceApps\_L4\_img-2.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt80ff7d7c1ff2bc95/67ddd1da782bfb1d246f5b22/BuildingMarketplaceApps_L4_img-2.png` |
| BuildingMarketplaceApps\_L4\_img-3.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt584d5bc4b9b2c57c/67ddd1f3983a65221b3b8134/BuildingMarketplaceApps_L4_img-3.png` |
| BuildingMarketplaceApps\_L4\_img-4.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltb20872c72a9d764f/67ddd211c566eea1c65e05ac/BuildingMarketplaceApps_L4_img-4.png` |
| BuildingMarketplaceApps\_L4\_img-5.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltca8399c374d7fda5/67ddd229c566ee43de5e05b0/BuildingMarketplaceApps_L4_img-5.png` |
| BuildingMarketplaceApps\_L4\_img-6.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blte073ab35b2984bbd/67ddd243443bd642a3f1d063/BuildingMarketplaceApps_L4_img-6.png` |
| BuildingMarketplaceApps\_L4\_img-7.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt54fc7725493b90a5/67ddd258632b9325acd4a744/BuildingMarketplaceApps_L4_img-7.png` |
| BuildingMarketApps\_L4\_img-8.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt0660fcf65e5af419/67ddd27cf7eccc8cb390dd93/BuildingMarketApps_L4_img-8.png` |
| BuildingMarketApps\_L4\_img-9.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt2801a882269d8baa/67ddd295cb02417c5f41baf5/BuildingMarketApps_L4_img-9.png` |
| BuildingMarketApps\_L4\_img-10.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt465d51e71080deaf/67ddd2ae37e25a0c8b1e4be3/BuildingMarketApps_L4_img-10.png` |
| BuildingMarketApps\_L4\_img-11.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltf7a9e25a026d071e/67ddd2c145b2297837911ba8/BuildingMarketApps_L4_img-11.png` |
| BuildingMarketApps\_L4\_img-12.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt998120cf9e693e24/67ddd3535e486df27471bb8b/BuildingMarketApps_L4_img-12.png` |
| BuildingMarketApps\_L4\_img-13.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt38f2afe06ed1a224/67ddd3683a0f383e7e00f05f/BuildingMarketApps_L4_img-13.png` |
| BuildingMarketApps\_L4\_img-14.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt9c735c8481833e23/67ddd37ea6d88e2861053a14/BuildingMarketApps_L4_img-14.png` |
| BuildingMarketApps\_L4\_img-15.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt8218c8dd05f02172/67ddd3930c6f554d721fd454/BuildingMarketApps_L4_img-15.png` |
| BuildingMarketApps\_L4\_img-16.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltfec1818564c23a2e/67ddd3a845b229ab4b911bb0/BuildingMarketApps_L4_img-16.png` |
| BuildingMarketApps\_L4\_img-17.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt9463c65f9c2664d9/67ddd3c15e486d864171bb91/BuildingMarketApps_L4_img-17.png` |
| BuildingMarketApps\_L4\_img-18.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt7d2a83f254f6b4bf/67ddd3d7a6d88e5250053a1b/BuildingMarketApps_L4_img-18.png` |
| BuildingMarketApps\_L4\_img-19.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blteedfa72cad67f1de/67ddd40cc9b8d450e7d14779/BuildingMarketApps_L4_img-19.png` |
| BuildingMarketApps\_L6\_img-1.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt65ba5d2c51370f0a/67ddd47a20a3dc1de3f82d1c/BuildingMarketApps_L6_img-1.png` |
| BuildingMarketApps\_L7\_img-1.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt3bdd6e185d07d7cd/67ddd4d120a3dc8816f82d21/BuildingMarketApps_L7_img-1.png` |
| BuildingMarketApps\_L7\_img-2.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt2801f429b2fb846d/67ddd4e84a89c3021ba308e8/BuildingMarketApps_L7_img-2.png` |
| BuildingMarketApps\_L7\_img-3.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt504f1d0eace9effc/67ddd4fcb1a1f3084c3efdc9/BuildingMarketApps_L7_img-3.png` |
| BuildingMarketApps\_L7\_img-4.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt8f85a7850c0adb36/67ddd510b1a1f303f33efdcf/BuildingMarketApps_L7_img-4.png` |
| BuildingMarketApps\_L7\_img-5.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt72d974f5cacafa58/67ddd527314d6717471b7525/BuildingMarketApps_L7_img-5.png` |
| BuildingMarketApps\_L7\_img-6.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltbf2ccc07a99ab0b1/67ddd539db243fef1c12c82d/BuildingMarketApps_L7_img-6.png` |
| BuildingMarketApps\_L8\_img-1.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt4cd2348d25be477d/67ddd582f7eccc6e4b90ddb2/BuildingMarketApps_L8_img-1.png` |
| BuildingMarketApps\_L8\_img-2.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt764c0be74ab65029/67ddd596b1a1f3788a3efdd8/BuildingMarketApps_L8_img-2.png` |
| BuildingMarketApps\_L8\_img-3.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt3de3dd3f8ab894ad/67ddd5b32d0b9838c7fd0b18/BuildingMarketApps_L8_img-3.png` |
| BuildingMarketApps\_L9\_img-1.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt042ebb93137ccbdc/67ddd6065e486d10db71bba7/BuildingMarketApps_L9_img-1.png` |
| BuildingMarketApps\_L9\_img-2.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt8051783e925f5d8d/67ddd61ac566ee763c5e05d7/BuildingMarketApps_L9_img-2.png` |
| BuildingMarketApps\_L10\_img-4.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blted35390bdd69fe1a/67ddd6c2a714582179b9bae8/BuildingMarketApps_L10_img-4.png` |
| BuildingMarketApps\_L10\_img-1.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt35b158833dab0fd8/67ddd65fa6d88e507e053a3a/BuildingMarketApps_L10_img-1.png` |
| BuildingMarketApps\_L10\_img-2.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt7a0fd085c01578dd/67ddd677cb0241239641bb1e/BuildingMarketApps_L10_img-2.png` |
| BuildingMarketApps\_L10\_img-3.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltb24e5863574c6103/67ddd68c17f7b4e3e20d0698/BuildingMarketApps_L10_img-3.png` |
| BuildingMarketApps\_L11\_img-1.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt93aed1b21138c5c7/67ddd703460eb4e71e947d6b/BuildingMarketApps_L11_img-1.png` |
| BuildingMarketApps\_L11\_img-2.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltb624c265797a4908/67ddd721f7ecccef8390ddcc/BuildingMarketApps_L11_img-2.png` |
| BuildingMarketApps\_L11\_img-3.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt7fd8bfdb21f67065/67ddd73a3a0f38bf6800f08b/BuildingMarketApps_L11_img-3.png` |
| BuildingMarketApps\_L11\_img-4.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltba037dbc6447399b/67ddd74e314d6730271b7543/BuildingMarketApps_L11_img-4.png` |
| BuildingMarketApps\_L12\_img-1.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt2f6625927a6db60e/67ddd9c6986725010d888f6f/BuildingMarketApps_L12_img-1.png` |
| BuildingMarketApps\_L12\_img-2.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltf05e3c45b26df693/67ddd9f0632b93877cd4a792/BuildingMarketApps_L12_img-2.png` |
| BuildingMarketApps\_L12\_img-3.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltcfc1c7bf4056b920/67ddda2b443bd6f690f1d0af/BuildingMarketApps_L12_img-3.png` |
| BuildingMarketApps\_L13\_img-1.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt979797fe9bc4c1d2/67e19cad914c24e15e497198/BuildingMarketApps_L13_img-1.png` |
| BuildingMarketApps\_L13\_img-2.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt51fdd596b74fba88/67e19cf8e5b23a6e6a9473b2/BuildingMarketApps_L13_img-2.png` |
| BuildingMarketApps\_L13\_img-3.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blte173e1cba4a4c832/67e19d24d148ed8ae2a3fd72/BuildingMarketApps_L13_img-3.png` |
| BuildingMarketApps\_L13\_img-4.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt0f30322a1f5db900/67e19f04b9f9a644badc4021/BuildingMarketApps_L13_img-4.png` |
| BuildingMarketApps\_L13\_img-5.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blteab33dc0720c7457/67e19f7f42bb03fbb96247ae/BuildingMarketApps_L13_img-5.png` |
| BuildingMarketApps\_L13\_img-6.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt5d06c6668747a45b/67e19fbc8f508c4aad13970d/BuildingMarketApps_L13_img-6.png` |
| BuildingMarketApps\_L13\_img-7.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blte7e41ae577695ea8/67e1a040e6ff226fb40d14da/BuildingMarketApps_L13_img-7.png` |

### External links

| Label | URL |
| --- | --- |
| Contentstack Academy home | `https://www.contentstack.com/academy/` |
| Training instance setup | `https://www.contentstack.com/academy/training-instance` |
| Academy playground (GitHub) | `https://github.com/contentstack/contentstack-academy-playground` |
| Contentstack documentation | `https://www.contentstack.com/docs/` |
| Starter App via the CLI | `https://www.contentstack.com/docs/developers/cli/bootstrap-starter-apps` |
| Marketplace | `https://app.contentstack.com/#!/marketplace/starters` |
| Github | `https://github.com/contentstack/marketplace-academycourse-app` |
| community.contentstack.com/ | `https://community.contentstack.com/` |
| https://community.contentstack.com/settings/general | `https://community.contentstack.com/settings/general` |
| Marketplace Apps | `https://www.contentstack.com/docs/developers/marketplace-apps` |
| https://github.com/contentstack/app-sdk-docs | `https://github.com/contentstack/app-sdk-docs` |
| NPM | `https://docs.npmjs.com/downloading-and-installing-node-js-and-npm` |
| NodeJS | `https://nodejs.org/en/download` |
| Developer Hub documentation | `https://www.contentstack.com/docs/developers/developer-hub` |
| BuildingMarketplaceApps\_L1\_img-1.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt9baf8e24ad9fac41/67ddd1beefd8a977054c06d5/BuildingMarketplaceApps_L1_img-1.png` |
| Log in to your Contentstack account | `https://www.contentstack.com/login/` |
| BuildingMarketplaceApps\_L4\_img-2.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt80ff7d7c1ff2bc95/67ddd1da782bfb1d246f5b22/BuildingMarketplaceApps_L4_img-2.png` |
| BuildingMarketplaceApps\_L4\_img-3.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt584d5bc4b9b2c57c/67ddd1f3983a65221b3b8134/BuildingMarketplaceApps_L4_img-3.png` |
| BuildingMarketplaceApps\_L4\_img-4.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltb20872c72a9d764f/67ddd211c566eea1c65e05ac/BuildingMarketplaceApps_L4_img-4.png` |
| BuildingMarketplaceApps\_L4\_img-5.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/bltca8399c374d7fda5/67ddd229c566ee43de5e05b0/BuildingMarketplaceApps_L4_img-5.png` |
| BuildingMarketplaceApps\_L4\_img-6.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blte073ab35b2984bbd/67ddd243443bd642a3f1d063/BuildingMarketplaceApps_L4_img-6.png` |
| BuildingMarketplaceApps\_L4\_img-7.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt54fc7725493b90a5/67ddd258632b9325acd4a744/BuildingMarketplaceApps_L4_img-7.png` |
| BuildingMarketApps\_L4\_img-8.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt0660fcf65e5af419/67ddd27cf7eccc8cb390dd93/BuildingMarketApps_L4_img-8.png` |
| BuildingMarketApps\_L4\_img-9.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt2801a882269d8baa/67ddd295cb02417c5f41baf5/BuildingMarketApps_L4_img-9.png` |
| BuildingMarketApps\_L4\_img-10.png | `https://images.contentstack.io/v3/assets/bltebc53cfaf0dd6403/blt465d51e71080deaf/67ddd2ae37e25a0c8b1e4be3/BuildingMarketApps_L4_img-10.png` |
