Marketplace apps - architecture, App SDK, and lifecycle
Marketplace apps: architecture, App SDK, and lifecycle
TL;DR:
- Marketplace apps run in sandboxed iframes and communicate with Contentstack through the App SDK's postMessage bridge.
- Five UI locations are available: Custom Field, Sidebar Widget, Dashboard Widget, Full-Page App, and App Configuration.
- Always call ContentstackAppSdk.init() before accessing any Contentstack data — it is asynchronous and location-aware.
Contentstack's Marketplace is not an app store in the traditional sense - it is an extensibility surface that lets you embed custom functionality directly into the Contentstack UI. Whether you install a pre-built integration from Contentstack's catalog or build a private app for your organization, the underlying architecture is the same: your code runs in a sandboxed iframe, communicates with the Contentstack host via the App SDK, and integrates seamlessly into the editorial experience. Understanding this architecture is essential before you write a single line of app code.
This lesson covers how Marketplace apps are structured, where they can appear in the Contentstack UI, how the App SDK connects your code to the platform, and the full lifecycle from creation to deployment.
The Marketplace catalog
The Marketplace is accessible from the left navigation of any stack under Marketplace. It contains two categories of apps:
- Contentstack-built apps - integrations maintained by Contentstack for common use cases: Algolia, Commercetools, Salesforce, Bynder, Cloudinary, and others.
- Custom apps - apps built by your organization, visible only within your Contentstack organization (private) or published for all Contentstack users (public).
When you install a Marketplace app into a stack, the app gains access to that stack according to the OAuth scopes and locations defined during app registration. Each installation is stack-specific - installing an app in your development stack does not automatically install it in production.
App locations: where apps appear in the UI
A Marketplace app defines one or more locations - specific places in the Contentstack UI where the app's interface renders. Each location serves a different purpose and receives different contextual data from the host.
Custom Field
A Custom Field app replaces a standard field in the entry editor. When an editor opens an entry, the custom field renders your app's UI instead of a native input. The app reads and writes data to the entry's field value, which is stored as part of the entry and returned via the Delivery API.
Use case: a color picker field, a product selector that queries an external commerce API, or a location picker that renders a map.
Sidebar Widget
A Sidebar Widget appears in the right-hand sidebar of the entry editor. It can read the current entry's data, modify field values, and communicate with external services. Unlike a Custom Field, a Sidebar Widget does not own a specific field - it operates alongside the entire entry.
Use case: an SEO scoring panel, a translation status tracker, or a content quality checklist.
Dashboard Widget
A Dashboard Widget appears on the stack's dashboard (the landing page when you open a stack). It provides at-a-glance information or quick actions without requiring editors to navigate to specific entries.
Use case: a content calendar, a publishing activity feed, or a task queue for pending reviews.
Full-Page App
A Full-Page App occupies the entire content area of the Contentstack UI and appears as its own item in the left navigation. It operates independently of any specific entry or content type.
Use case: an analytics dashboard, a bulk operations tool, or a content migration interface.
App Configuration
The App Configuration location renders when a stack administrator configures the app after installation. It provides a UI for storing per-stack settings - API keys for third-party services, default values, feature toggles, or mapping configurations.
Use case: entering an Algolia API key and index name, selecting which content types should be indexed, or configuring a default language for translation services.
The App SDK: connecting your code to Contentstack
The @contentstack/app-sdk package is the JavaScript library that bridges your app's iframe with the Contentstack host application. All communication between your app and Contentstack flows through this SDK.
How iframe communication works
Marketplace apps run inside iframes. Your app's HTML, CSS, and JavaScript are served from your hosting infrastructure (or Contentstack's Launch platform). The Contentstack UI loads your app in an iframe and establishes a postMessage communication channel. The App SDK abstracts this channel, providing a typed API instead of raw message passing.
This architecture means your app:
- Runs in a sandboxed browser context (cannot access the parent window's DOM directly).
- Can be built with any frontend framework (React, Vue, Svelte, vanilla JavaScript).
- Must initialize the App SDK before accessing any Contentstack data.
- Is subject to standard browser security policies for iframes.
Initializing the SDK
Every Marketplace app starts by calling ContentstackAppSdk.init(). This asynchronous call establishes the communication channel with the Contentstack host and returns a location-specific interface.
import ContentstackAppSdk from "@contentstack/app-sdk";
ContentstackAppSdk.init().then((sdk) => {
// sdk contains location-specific methods and data
const location = sdk.location;
// Determine which location the app is rendering in
if (location.CustomField) {
// Custom Field context
const fieldInstance = location.CustomField;
const currentValue = fieldInstance.field.getData();
console.log("Current field value:", currentValue);
}
if (location.SidebarWidget) {
// Sidebar Widget context
const sidebarInstance = location.SidebarWidget;
const entry = sidebarInstance.entry.getData();
console.log("Current entry:", entry);
}
});The sdk object exposes different properties depending on the location. A Custom Field instance has field.getData() and field.setData(). A Sidebar Widget instance has entry.getData() and methods to interact with the entry's fields. A Dashboard Widget has stack-level context but no entry-specific data.
Key SDK methods by location
Custom Field:
const field = location.CustomField.field;
// Read the current field value
const data = field.getData();
// Write a new value to the field
await field.setData({ productId: "SKU-1234", productName: "Widget Pro" });
// Listen for external changes to the field
field.onChange((newValue) => {
console.log("Field value changed:", newValue);
});
// Set the field's validity (shows error state in the UI)
field.setValidity(false, "Please select a product");Sidebar Widget:
const sidebar = location.SidebarWidget; // Read the full entry data const entryData = sidebar.entry.getData(); // Get a specific field's value const title = sidebar.entry.getData().title; // Get the content type UID const contentTypeUid = sidebar.entry.content_type.uid; // Access stack information const stackInfo = sdk.stack; const apiKey = stackInfo._data.api_key;
App Configuration:
const config = location.AppConfigWidget;
// Read existing configuration
const existingConfig = await config.getInstallationData();
// Save configuration data
await config.setInstallationData({
algoliaAppId: "ABCDEF1234",
algoliaApiKey: "your-admin-api-key",
indexName: "products",
});Accessing stack context
Regardless of location, every app has access to the sdk.stack object, which provides methods to interact with the stack's content:
const stack = sdk.stack;
// Query entries from a content type
const response = await stack.ContentType("product").Entry.Query()
.where("locale", "en-us")
.find();
// Get a specific entry
const entry = await stack.ContentType("product")
.Entry("blt_matrix_link_001")
.fetch();
// Access asset information
const asset = await stack.Asset("blt0987654321fedcba").fetch();The app lifecycle
Marketplace apps follow a defined lifecycle from creation through ongoing use:
1. Create the app in Developer Hub
Navigate to Developer Hub (accessible at https://app.contentstack.com/#!/developerhub or via your organization's settings). Click New App and provide:
- App name and description
- App locations (which UI locations this app supports)
- OAuth scopes (what permissions the app requests)
- App URL for each location (where your hosted code lives)
- Webhook URL (if the app needs to receive server-side events)
2. Define locations and URLs
For each location your app supports, specify the URL that Contentstack will load in the iframe. During development, this is typically http://localhost:3000. In production, it is the deployed URL of your app.
Custom Field URL: https://my-app.example.com/custom-field Sidebar Widget URL: https://my-app.example.com/sidebar App Configuration URL: https://my-app.example.com/config
Each URL can be a different route in the same application or a separate deployment entirely. The App SDK initialization detects the current location context automatically.
3. Set OAuth scopes
Apps request specific permissions through OAuth scopes. Only request the scopes your app actually needs:
- cm.content-types.management:read - read content type schemas
- cm.entries.management:read - read entries via CMA
- cm.entries.management:write - create or update entries
- cm.assets.management:read - read assets
- cm.stacks.management:read - read stack configuration
Requesting excessive scopes triggers security concerns during app review and may cause administrators to reject the installation.
4. Install in a stack
From the stack's Marketplace section, find your app (listed under your organization's apps for private apps) and click Install. The installation process:
- Presents the OAuth consent screen showing requested scopes.
- Runs the App Configuration location (if defined) so the administrator can enter settings.
- Activates the app in all defined locations for that stack.
5. Use, update, and uninstall
Once installed, the app appears in the locations defined during registration. Editors interact with it as part of their normal workflow.
Updating: Change your app's hosted code, and the updates are reflected immediately (the iframe loads your URL each time). To change locations, scopes, or metadata, update the app registration in Developer Hub.
Uninstalling: Stack administrators can uninstall the app from Marketplace > Installed Apps. Uninstalling removes the app from all locations in that stack. Any data the app stored in entry fields (via Custom Field) remains in the entries - the data persists even if the app is removed.
Building an SEO Score sidebar widget: a worked example
To make these concepts concrete, let us walk through the architecture of a Sidebar Widget that analyzes the current entry's content and displays an SEO quality score.
Requirements
- Appears in the sidebar when editors open any Article entry.
- Reads the entry's title, meta_description, and body fields.
- Calculates an SEO score based on title length, meta description length, keyword density, and heading structure.
- Displays the score with specific recommendations.
- Does not modify the entry - it is read-only.
App registration
In Developer Hub, create a new app with:
- Location: Sidebar Widget
- URL: https://seo-widget.example.com
- OAuth scopes: cm.entries.management:read (read-only access to entry data)
Implementation structure
import ContentstackAppSdk from "@contentstack/app-sdk";
import { useState, useEffect } from "react";
function SeoScoreWidget() {
const [score, setScore] = useState(null);
const [recommendations, setRecommendations] = useState([]);
useEffect(() => {
ContentstackAppSdk.init().then((sdk) => {
const sidebar = sdk.location.SidebarWidget;
const entryData = sidebar.entry.getData();
const title = entryData.title || "";
const metaDescription = entryData.meta_description || "";
const body = entryData.body || "";
const result = calculateSeoScore(title, metaDescription, body);
setScore(result.score);
setRecommendations(result.recommendations);
// Listen for entry changes to recalculate in real time
sidebar.entry.onChange((updatedEntry) => {
const updated = calculateSeoScore(
updatedEntry.title || "",
updatedEntry.meta_description || "",
updatedEntry.body || ""
);
setScore(updated.score);
setRecommendations(updated.recommendations);
});
});
}, []);
return (
<div classname="seo-widget">
<h3>SEO Score</h3>
{score !== null && (
<>
<div classname="{`score-badge" ${score="">= 80 ? "good" : score >= 50 ? "fair" : "poor"}`}>
{score}/100
</div>
<ul>
{recommendations.map((rec, i) => (
<li key="{i}">{rec}</li>
))}
</ul>
)}
</div>
);
}
function calculateSeoScore(title, metaDescription, body) {
const recommendations = [];
let score = 100;
// Title length check (optimal: 50-60 characters)
if (title.length < 30) {
score -= 20;
recommendations.push("Title is too short. Aim for 50-60 characters.");
} else if (title.length > 65) {
score -= 10;
recommendations.push("Title may be truncated in search results. Keep under 60 characters.");
}
// Meta description check (optimal: 120-160 characters)
if (!metaDescription) {
score -= 25;
recommendations.push("Missing meta description. Add a 120-160 character summary.");
} else if (metaDescription.length < 120) {
score -= 10;
recommendations.push("Meta description is short. Aim for 120-160 characters.");
}
// Body content check
if (body.length < 300) {
score -= 20;
recommendations.push("Body content is thin. Search engines prefer substantial content.");
}
if (recommendations.length === 0) {
recommendations.push("Content meets SEO best practices.");
}
return { score: Math.max(0, score), recommendations };
}This widget reads entry data through the App SDK, performs analysis entirely in the browser, and provides real-time feedback as editors modify fields. It requires no backend service - the SEO calculation logic runs client-side within the iframe.
Private vs public apps
When you create an app in Developer Hub, it is private by default - visible and installable only within your Contentstack organization. This is appropriate for internal tools, proprietary integrations, and apps that access your organization's specific infrastructure.
Public apps are submitted to Contentstack for review and, once approved, become available to all Contentstack users in the Marketplace catalog. Publishing a public app requires:
- Thorough testing across different stack configurations.
- Documentation for installation and configuration.
- Compliance with Contentstack's app review guidelines.
- A support plan for users who install your app.
For the purposes of this certification, most apps you build will be private apps scoped to your organization.
Common mistakes
Common pitfall:
Calling sdk.location before ContentstackAppSdk.init() resolves produces undefined values and silent failures. Always gate your UI rendering on SDK initialization completing.
- Forgetting to call ContentstackAppSdk.init() before accessing data. The SDK initialization is asynchronous and must complete before you can read entry data, stack context, or configuration. Structure your app so that UI rendering waits for SDK initialization.
- Requesting excessive OAuth scopes. An app that only reads entry data should not request write scopes. Requesting cm.entries.management:write when your app only displays information creates unnecessary security risk and may cause administrators to question the installation. Follow the principle of least privilege.
- Assuming all locations have the same context. A Sidebar Widget has access to entry.getData(), but a Dashboard Widget does not - no “current entry” exists on the dashboard. Writing code that assumes entry context exists in all locations leads to runtime errors. Always check which location is active before accessing location-specific methods.