Marketplace App Boilerplate
A boilerplate is a fitting template to describe distinct repetitive segments of a project to help build projects quickly and efficiently.
They can define project-level elements or standard methods for one or more projects.
The following guide shows how to build an app using our Marketplace App boilerplate. For more information about the Marketplace App boilerplate, you can check the GitHub repository here.
Why should you use the Marketplace App Boilerplate?
- The boilerplate code includes all categories of applications you can create in Contentstack, i.e., custom fields, sidebar extensions, and dashboard extensions.
- Creating any application is prompt since you only need to use the required routes and corresponding components.
- We have built a boilerplate that incorporates all the best practices you can use while building your application in Contentstack.
- With this template, you can save a considerable amount of development time.
- You can use the JSON RTE plugin within the Boilerplate App. For more information, please refer to the JSON RTE plugin documentation.
Structure of the Marketplace App Boilerplate
The boilerplate folder structure consists of relative files and references, making it easy to acclimate within your project. This structure also allows the boilerplate to be thoroughly portable between different stacks in Contentstack.
Below is the folder structure of the boilerplate:
MARKETPLACE-APP-BOILERPLATE
├─ e2e
└─ public
├─ src
| ├─ assets
| └─ common
| | ├─ contexts
| | └─ hooks
| | └─ locales/en-us
| | └─ providers
| | | ├─ AppConfigurationExtensionProvider.tsx
| | | └─ CustomFieldExtensionProvider.tsx
| | | └─ EntrySidebarExtensionProvider.tsx
| | | └─ MarketplaceAppProvider.tsx
| | └─ types
| | └─ utils
| └─ components
| └─ containers
| | ├─ 404
| | └─ App
| | └─ AppConfiguration
| | └─ AssetSidebarWidget
| | └─ CustomField
| | └─ DashboardWidget
| | └─ FieldModifier
| | └─ FullPage
| | └─ SidebarWidget
| | └─ index.tsx
| └─ test-utils
| └─ index.css
| └─ index.tsx
| └─ react-app-env.d.ts
| └─ reportWebVitals.ts
| └─ setupTests.ts
└─ .editorconfig
└─ .env.sample
└─ .gitignore
└─ .prettierrc
└─ CODEOWNERS
└─ README.md
└─ global-setup.ts
└─ global-teardown.ts
└─ LICENSE
└─ manifest.json
└─ package-lock.json
└─ package.json
└─ playwright.config.ts
└─ tsconfig.json
Below are the app routes for each location in App.tsx: You can check the folder containing the file as shown below: src/containers/App/App.tsx
function App() {
return (
<ErrorBoundary>
<MarketplaceAppProvider>
<Routes>
<Route path="/" element={<DefaultPage />} />
<Route
path="/custom-field"
element={
<Suspense>
<CustomFieldExtensionProvider>
<CustomFieldExtension />
</CustomFieldExtensionProvider>
</Suspense>
}
/>
<Route
path="/entry-sidebar"
element={
<Suspense>
<EntrySidebarExtensionProvider>
<EntrySidebarExtension />
</EntrySidebarExtensionProvider>
</Suspense>
}
/>
<Route
path="/app-configuration"
element={
<Suspense>
<AppConfigurationExtensionProvider>
<AppConfigurationExtension />
</AppConfigurationExtensionProvider>
</Suspense>
}
/>
<Route
path="/asset-sidebar"
element={
<Suspense>
<AssetSidebarExtension />
</Suspense>
}
/>
<Route
path="/stack-dashboard"
element={
<Suspense>
<StackDashboardExtension />
</Suspense>
}
/>
<Route
path="/full-page"
element={
<Suspense>
<FullPageExtension />
</Suspense>
}
/>
<Route
path="/field-modifier"
element={
<Suspense>
<FieldModifierExtension />
</Suspense>
}
/>
<Route path="*" element={<PageNotFound />} />
</Routes>
</MarketplaceAppProvider>
</ErrorBoundary>
);
}
Using Marketplace App Boilerplate To Develop Custom Applications
To get started with building applications using the boilerplate, follow the steps given below:
Prerequisites
- Contentstack account
- App Boilerplate Framework
- Reference to App SDK
Install Dependencies
- Navigate to the root directory of the downloaded zip file.
- Run the following command to install the necessary packages:
npm install
- After you install the packages, run the following command to get started:
npm start
Creating Project Using The Boilerplate
To use your application, you need to upload it to Contentstack. To do so, perform the steps given below:
- Log in to your Contentstack account.
- In the left-hand-side primary navigation, you will find a new icon for Developer Hub (as shown below). Click the icon to go to the Developer Hub.
- Click the + New App button.
- In the New App modal, give a suitable name and description.
- Click Create.
- On the resulting Basic information page, upload your app's icon and Save the changes.
- Click the UI Locations icon. On the resulting page enter the App URL.
- Add the UI Locations as per your requirement.
- Add the below routes for each UI Location to get the desired results.
Note: The name for each UI Location is optional, and can be used to override the default app name.
- Stack Dashboard
For the Stack Dashboard UI Location, use #/stack-dashboard for Path.
- Asset Sidebar
For the Asset Sidebar UI Location, use #/asset-sidebar for Path.
- Custom Field
For the Custom Field UI Location, use #/custom-field for Path. Enter Text for Data Type. - Entry Sidebar
For the Entry Sidebar UI Location, use #/entry-sidebar for Path. - App Configuration
For the App Configuration UI Location, use #/app-configuration for Path. - Full Page
For the Full Page UI Location, use Full Pagefor name and #/full-page for Path. - Field Modifier
For the Field Modifier UI Location, use Field Modifierfor name and #/field-modifier for Path.Note: After adding each route save and install the app in any stack.
- Stack Dashboard
For the Stack Dashboard UI Location, use #/stack-dashboard for Path.
- Select the stack where you want to install the app and click the Install button.
- You will be redirected to the configuration page of the app.
- On the Configuration page, enter the values for Sample App Configuration Field and Sample Server Configuration Field.
Let’s understand the configuration fields:
- Sample App Configuration Field: You can save non-sensitive data that you want to show in different UI locations. For example, if you want to create a form with Username, Date, Email Address, etc. then, you can add the value in the field and view the data in the configured UI location(s).
- Sample Server Configuration Field: You can save sensitive data. For example, if you want to create a form with Password, Client Secret, and Client ID then, you can enter a value in the Sample Server Configuration Field and the value will be stored in the backend via webhooks.
Additional Resource: To learn more, refer to the App Configuration document.
Let’s understand how you can use the Sample App Configuration and Sample Server Configuration fields. These fields act as a template to use before developing an application. You can save the values in these fields based on the requirement (sensitive and non-sensitive) and view them in the configured UI location(s).
Here is a sample code snippet for Sample App Configuration Field and Sample Server Configuration Field:
import React, { useRef } from "react"; import Icon from "../../assets/GearSix.svg"; import localeTexts from "../../common/locales/en-us/index"; import parse from "html-react-parser"; import styles from "./AppConfiguration.module.css"; import { useInstallationData } from "../../common/hooks/useInstallationData"; import Tooltip from "../Tooltip/Tooltip"; const AppConfigurationExtension: React.FC = () => { const { installationData, setInstallationData } = useInstallationData(); const appConfigDataRef = useRef<any>(""); const serverConfigDataRef = useRef<any>(""); const updateConfig = async (elem: any) => { if (typeof setInstallationData !== "undefined") { await setInstallationData({ configuration: { "Sample App Configuration": appConfigDataRef.current.value }, serverConfiguration: { "Sample Server Configuration": serverConfigDataRef.current.value }, }); } }; return ( <div className={`${styles.layoutContainer}`}> <div className={`${styles.appConfig}`}> <div className={`${styles.appConfigLogoContainer}`}> <img src={Icon} alt="icon" /> <p>{localeTexts.ConfigScreen.title}</p> </div> {/* THIS IS THE SAMPLE APP CONFIGURATION FIELD */} <div className={`${styles.configWrapper}`}> <div className={`${styles.configContainer}`}> <div className={`${styles.infoContainerWrapper}`}> <div className={`${styles.infoContainer}`}> <div className={`${styles.labelWrapper}`}> <label htmlFor="appConfigData">Sample App Configuration Field </label> <Tooltip content="You can save this field for information such as Username, Email, Number, Date, etc." /> </div> </div> <div className={`${styles.inputContainer}`}> <input type="text" ref="{appConfigDataRef}" required="" value="{installationData.configuration.appConfigData}" placeholder="Enter Field Value" name="appConfigData" autocomplete="off" classname="{`${styles.fieldInput}`}" onchange="{updateConfig}"></input> </div> </div> <div className={`${styles.descriptionContainer}`}> <p> Use this field to share non-sensitive configurations of your app with other locations. </p> </div> </div> {/* THIS IS THE SAMPLE SERVER CONFIGURATION FIELD */} <div className={`${styles.configContainer}`}> <div className={`${styles.infoContainerWrapper}`}> <div className={`${styles.infoContainer}`}> <div className={`${styles.labelWrapper}`}> <label htmlFor="serverConfigData">Sample Server Configuration Field </label> <Tooltip content="You can use this field for information such as Passwords, API Key, Client Secret, Client ID, etc." /> </div> </div> <div className={`${styles.inputContainer}`}> <input type="text" ref="{serverConfigDataRef}" required="" value="{installationData.serverConfiguration.serverConfigData}" placeholder="Enter Field Value" name="serverConfigData" autocomplete="off" onchange="{updateConfig}"></input> </div> </div> <div className={`${styles.descriptionContainer}`}> <p> Use this field to store sensitive configurations of your app. It is directly shared with the backend via webhooks. </p> </div> </div> </div> <div className={`${styles.locationDescription}`}> <p className={`${styles.locationDescriptionText}`}>{parse(localeTexts.ConfigScreen.body)}</p> <a target="_blank" rel="noreferrer" href={localeTexts.ConfigScreen.button.url}> <span className={`${styles.locationDescriptionLink}`}>{localeTexts.ConfigScreen.button.text}</span> </a> </div> </div> </div> ); }; export default AppConfigurationExtension;
Suppose, if you want to develop an application, you can customize the Sample App Configuration Field and the Sample Server Configuration Field.
For example, you need to display Username and Email on the Stack Dashboard UI location whereas the Secret Key will be stored in the server configuration. You must add the configuration details in the App.Configuration.tsx file as shown below:
Here is the code snippet that will help you understand.
import React, { useRef } from "react"; import Icon from "../../assets/GearSix.svg"; import localeTexts from "../../common/locales/en-us/index"; import parse from "html-react-parser"; import styles from "./AppConfiguration.module.css"; import { useInstallationData } from "../../common/hooks/useInstallationData"; import Tooltip from "../Tooltip/Tooltip"; const AppConfigurationExtension: React.FC = () => { const { installationData, setInstallationData } = useInstallationData(); const usernameRef = useRef<any>(""); const emailRef = useRef<any>(""); const secretRef = useRef<any>(""); const updateConfig = async (elem: any) => { if (typeof setInstallationData !== "undefined") { await setInstallationData({ configuration: { Username: usernameRef.current.value, Email: emailRef.current.value }, serverConfiguration: { Secret: secretRef.current.value }, }); } }; return ( <div className={`${styles.layoutContainer}`}> <div className={`${styles.appConfig}`}> <div className={`${styles.appConfigLogoContainer}`}> <img src={Icon} alt="icon" /> <p>{localeTexts.ConfigScreen.title}</p> </div> <div className={`${styles.configWrapper}`}> {/* THIS IS THE APP CONFIGURATION FIELD */} {/* SAVING USERNAME */} <div className={`${styles.configContainer}`}> <div className={`${styles.infoContainerWrapper}`}> <div className={`${styles.infoContainer}`}> <div className={`${styles.labelWrapper}`}> <label htmlFor="username">Username</label> <Tooltip content="Please enter the username you wish to use in the application you just installed" /> </div> </div> <div className={`${styles.inputContainer}`}> <input type="text" ref="{usernameRef}" required="" value="{installationData.configuration.username}" placeholder="Enter Field Value" name="username" autocomplete="off" classname="{`${styles.fieldInput}`}" onchange="{updateConfig}"></input> </div> </div> <div className={`${styles.descriptionContainer}`}> {/* <p> Use this field to share non-sensitive configurations of your app with other locations. </p> */} </div> </div> {/* SAVING THE USER'S EMAIL ID FOR MAIL UPDATES */} <div className={`${styles.configContainer}`}> <div className={`${styles.infoContainerWrapper}`}> <div className={`${styles.infoContainer}`}> <div className={`${styles.labelWrapper}`}> <label htmlFor="email">Email</label> <Tooltip content="Please enter the email you wish to use in the application you just installed" /> </div> </div> <div className={`${styles.inputContainer}`}> <input type="text" ref="{emailRef}" required="" value="{installationData.configuration.email}" placeholder="Enter Field Value" name="email" autocomplete="off" classname="{`${styles.fieldInput}`}" onchange="{updateConfig}"></input> </div> </div> <div className={`${styles.descriptionContainer}`}> {/* <p> Use this field to share non-sensitive configurations of your app with other locations. </p> */} </div> </div> {/* THIS IS THE SERVER CONFIGURATION FIELD */} {/* SAVING THE USER'S SECRET ID */} <div className={`${styles.configContainer}`}> <div className={`${styles.infoContainerWrapper}`}> <div className={`${styles.infoContainer}`}> <div className={`${styles.labelWrapper}`}> <label htmlFor="secret">Secret key</label> <Tooltip content="Enter your Secret Key you wish to set for your Application" /> </div> </div> <div className={`${styles.inputContainer}`}> <input type="text" ref="{secretRef}" required="" value="{installationData.serverConfiguration.secret}" placeholder="Enter Field Value" name="secret" autocomplete="off" onchange="{updateConfig}"></input> </div> </div> <div className={`${styles.descriptionContainer}`}> {/* <p> Use this field to store sensitive configurations of your app. It is directly shared with the backend via webhooks. </p> */} </div> </div> </div> <div className={`${styles.locationDescription}`}> <p className={`${styles.locationDescriptionText}`}>{parse(localeTexts.ConfigScreen.body)}</p> <a target="_blank" rel="noreferrer" href={localeTexts.ConfigScreen.button.url}> <span className={`${styles.locationDescriptionLink}`}>{localeTexts.ConfigScreen.button.text}</span> </a> </div> </div> </div> ); }; export default AppConfigurationExtension;
Now, to use the above configurations in the Stack Dashboard UI location, follow the code snippet as shown below and use it within the UI location file.
import { useCallback, useState } from "react"; import localeTexts from "../../common/locales/en-us/index"; import parse from "html-react-parser"; import { useAppConfig } from "../../common/hooks/useAppConfig"; import "../index.css"; import "./StackDashboard.css"; import Icon from "../../assets/Custom-Field-Logo.svg"; import ReadOnly from "../../assets/lock.svg"; import JsonView from "../../assets/JsonView.svg"; import ConfigModal from "../../components/ConfigModal/ConfigModal"; const StackDashboardExtension = () => { const appConfig = useAppConfig(); const [isRawConfigModalOpen, setRawConfigModalOpen] = useState<boolean>(false); const handleViewRawConfig = useCallback(() => { setRawConfigModalOpen(true); }, []); const handleCloseModal = useCallback(() => { setRawConfigModalOpen(false); }, []); const username = appConfig?.["Username"] || ""; const trimmedUsername = username.length > 17 ? `${username.substring(0, 17)}...` : username; const email = appConfig?.["Email"] || ""; const trimmedEmail = username.length > 17 ? `${email.substring(0, 17)}...` : email; return ( <div className="layout-container"> <div className="ui-location"> <div className="ui-container"> <div className="logo-container"> <img src={Icon} alt="Logo" /> <p>{localeTexts.DashboardWidget.title}</p> </div> <div className="config-container"> <div className="label-container"> <p className="label">Username</p> <p className="info">(read only)</p> <img src={JsonView} style={{marginLeft:'70px'}} alt="Show-Json-CTA" className="show-json-cta" onClick={handleViewRawConfig} /> {isRawConfigModalOpen && <ConfigModal config={appConfig!} onClose={handleCloseModal} />} </div> <div className="input-wrapper"> <div className="input-container"> <p className="config-value">{trimmedUsername}</p> <img src={ReadOnly} alt="ReadOnlyLogo" /> </div> </div> </div> <div className="config-container"> <div className="label-container"> <p className="label">Email</p> <p className="info">(read only)</p> </div> <div className="input-wrapper"> <div className="input-container"> <p className="config-value">{trimmedEmail}</p> <img src={ReadOnly} alt="ReadOnlyLogo" /> </div> </div> </div> </div> </div> </div> ); }; export default StackDashboardExtension;
- Click Save and click Open Stack to start using the application.
- Navigate to the stack where your application is installed and view your application in the configured UI location.
Note: You must open the app in the configured UI location to view it.
Installing JSON RTE Plugin
To install the JSON RTE Plugin, refer to the Create JSON RTE Plugin documentation.
Integrating Venus Component Library
Venus Components can be used in creating a React app or any React app build that uses webpack or any other bundler.
Follow the instructions given below to integrate the components with existing UI extensions built using React.
Installation
Run the following command to install the Venus Component Library elements:
npm i @contentstack/venus-components@1.0.5 --legacy-peer-deps
Usage
Next, use the following code snippet to integrate the venus components into your file. Import the component from venus library and use it as shown below. Here we have imported Heading and Button from the venus library.
import React from 'react';
import ReactDOM from 'react-dom';
import ContentstackUIExtension from "@contentstack/ui-extensions-sdk";
import { Button, Heading } from '@contentstack/venus-components';
import '@contentstack/venus-components/build/main.css';
class App extends React.Component {
render() {
return (
<Heading tagName="h2" text="Extension building using Venus component" />
<Button
buttonType="primary"
onClick={() => {
console.log('You clicked on Venus button');
}}
>
Click on me
</Button>
);
}
}
ContentstackUIExtension.init().then((extension) => {
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
})
Using Modal Component for Fullscreen View:
Modal component for fullscreen view can be added for any of the UI locations. Let us consider Stack Dashboard UI location as an example here:
Step 1: In the App Boilerplate template, go to /src/common/providers/MarketplaceAppProvider.tsx file and add the following in the UseEffect()
useEffect(() => {
ContentstackAppSDK.init()
.then(async (appSdk) => {
//@ts-ignore
window.postRobot = appSdk.postRobot;
setAppSdk(appSdk);
const appConfig = await appSdk.getConfig();
setConfig(appConfig);
})
.catch(() => {
setFailed(true);
});
}, []);
Step 2: Now go to /src/components/ and create a Fullscreen component “FullScreenPage.tsx” to integrate within your app
import React from "react";
import "@contentstack/venus-components/build/main.css";
import { Icon } from "@contentstack/venus-components";
export type fullScreenProps = {
closeModal: () => void;
children: React.ReactNode;
};
const FullScreenPage: React.FC<fullScreenProps> = ({ closeModal, children }) => {
return (
<div
style={{
width: "calc(100vw - 100px)",
height: "calc(100vh - 100px)",
borderRadius: "inherit",
overflow: "hidden",
display: "flex",
flexDirection: "column",
}}>
<div className="flex FullPage_Modal_Header">
<h6 style={{ margin: "auto auto auto 22px" }}>FullScreen View</h6>
<Icon
icon="Compress"
size="small"
className="Tab__icon mt-20"
hover={true}
hoverType="secondary"
style={{ marginRight: "30px", marginLeft: "auto", cursor: "pointer" }}
onClick={() => {
closeModal();
}}
/>
</div>
<div className="fullscreen h-full">{children}</div>
</div>
);
};
export default FullScreenPage;
Step 3: Create a CustomComponent.tsx file inside /src/components/ which will be used to render in the fullscreen mode.
const CustomComponent = () => {
return (
<div style={{ margin: "10px", padding: "10px" }}>
<h1>Title</h1>
<p style={{ textAlign: "justify", padding: "8px" }}>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolore, ducimus doloremque eum, a dolorum repudiandae,
nostrum quas quae dolor tenetur saepe. Voluptas eaque praesentium ab velit consequatur deserunt totam, hic
quidem, ipsam blanditiis vitae tempore nostrum officia tempora magni repudiandae consectetur laborum sint
adipisci ex minima quas soluta esse id? Cupiditate saepe corporis suscipit! Molestias maiores quae blanditiis
ipsa possimus, repudiandae cum? Iure deserunt quam blanditiis et?
</p>
</div>
);
};
export default CustomComponent;
Step 4: Go to /src/containers/DashboardWidget/StackDashboard.tsx file and add the following:
import { useEffect, useRef } from "react";
import { useAppSdk } from "../../common/hooks/useAppSdk";
import { cbModal } from "@contentstack/venus-components";
import FullScreenPage from "../../components/FullScreenPage";
import { ReactComponent as FullscreenIcon } from "../../assets/fullscreen.svg";
import CustomComponent from "../../components/CustomComponent";
interface StackDashboardExtensionProps {
fullScreen?: boolean;
}
/** Stack Dashboard page component. */
const StackDashboardExtension = ({ fullScreen }: StackDashboardExtensionProps) => {
const ref = useRef(null);
const appSdk = useAppSdk();
useEffect(() => {
if (!fullScreen) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
window.iframeRef = ref.current;
}
appSdk?.location.DashboardWidget?.frame.updateHeight(600);
}, []);
const openModal = () => {
cbModal({
component: (modalProps: any) => (
<FullScreenPage {...modalProps}>
<StackDashboardExtension fullScreen={true} />
</FullScreenPage>
),
modalProps: {
size: "customSize",
},
});
};
let dynamicClassName = fullScreen ? "fullScreenWrapper" : "h-screen";
return (
<div ref={ref} className={dynamicClassName}>
<CustomComponent />
{!fullScreen && (
<FullscreenIcon
className="fullscreenBtn"
type="button"
title="Fullscreen"
onClick={openModal}
style={{ position: "fixed", top: "10px", right: " 10px" }}
/>
)}
</div>
);
};
export default StackDashboardExtension;
Next Step
Next, you can refer to the Get Started with Building Apps using Contentstack’s App SDK guide to start using the Contentstack App SDK in creating your apps.