A Contentstack marketplace app that integrates Brandfolder DAM with Contentstack's content management system. This React-based application provides App Configuration, Custom Field, Selector Page, and JSON RTE locations for browsing and embedding Brandfolder assets.
- App Configuration: Configure app settings and Brandfolder connection from the Config Screen
- Custom Field: Embed and manage Brandfolder assets in entry fields (card and list views)
- Selector Page: Full-page asset picker for choosing Brandfolder assets
- JSON RTE: Rich text editor extension for inserting Brandfolder assets
- TypeScript: Fully typed for better development experience
- Testing: Comprehensive test suite with Playwright for E2E testing
- Modern UI: Clean interface using Contentstack's Venus components and Brandfolder Panel SDK
- Node.js (v20 or higher)
- npm
- Contentstack Account
- Brandfolder account (for DAM integration)
# Clone the repository
git clone https://github.com/contentstack/marketplace-brandfolder-app.git
cd marketplace-brandfolder-app
# Install dependencies (root)
npm install
# Install UI dependencies
cd ui
npm install
cd ..Copy or rename each .env.example to .env in the repo root (Playwright e2e), ui/, and ui/rte/. Do not commit real secrets.
Root (.env at repo root) β Playwright e2e and local tooling. Mirror .env.example in full; summary:
| Group | Variables | Notes |
|---|---|---|
| Contentstack auth | CONTENTSTACK_LOGIN, CONTENTSTACK_PASSWORD |
Required for e2e global setup |
| MFA (if enabled) | CONTENTSTACK_TFA_TOKEN and/or CONTENTSTACK_MFA_SECRET |
Use a TOTP secret for stable automation, or a short-lived 6-digit token |
| Org / APIs | CONTENTSTACK_ORGANIZATION_UID, BASE_API_URL, DEVELOPER_HUB_API |
API hosts only (no path or #!); use your regionβs hosts per Contentstack docs |
| App URLs | APP_HOST_URL, APP_BASE_URL |
Example defaults: http://localhost:4000 and http://localhost:4000/# |
| CMS web app | CONTENTSTACK_REGION, ENV_URL |
e.g. NA + https://app.contentstack.com; EU: set region or ENV_URL=https://eu-app.contentstack.com |
| QA marketplace flow | ORGNAME, STACKNAME, BRANDFOLDER |
Org display name and stack/app names (see tests/e2e/test-spec/brandfolder-flow.spec.ts) |
| Stack under test | STACK_API_KEY, STACK_NAME |
STACK_NAME = display name in Marketplace β Installed Apps (needed for QA-style config + asset e2e) |
| Optional | BASIC_AUTH_USERNAME, BASIC_AUTH_PASSWORD |
HTTP basic auth in front of CMS |
| Optional | BRANDFOLDER_API_KEY1, BRANDFOLDER_API_KEY2, BRANDFOLDER_API_KEY3 |
DAM keys (QA parity with legacy automation) |
| Optional | BRANDFOLDER_APP_NAME |
Extension lookup when resolving field UID |
| Optional | INSTALL_VIA_MARKETPLACE, USE_GLOBAL_TEARDOWN |
See commented notes in .env.example |
| Tooling | SKIP_PREFLIGHT_CHECK |
e.g. true (as in .env.example) |
UI (ui/.env):
VITE_CUSTOM_FIELD_URL=YOUR_CUSTOM_FIELD_DOMAIN_URL
VITE_REGION_MAPPING='{"NA": {"JSON_RTE_URL": "https://rte-extension.contentstack.com"},"EU": {"JSON_RTE_URL": "https://eu-rte-extension.contentstack.com"},"AZURE_NA": {"JSON_RTE_URL": "https://azure-na-rte-extension.contentstack.com"},"AZURE_EU": {"JSON_RTE_URL": "https://azure-eu-rte-extension.contentstack.com"},"GCP_NA": {"JSON_RTE_URL": "https://gcp-na-rte-extension.contentstack.com"}}'
RTE (ui/rte/.env):
REACT_APP_CUSTOM_FIELD_URL=YOUR_CUSTOM_FIELD_DOMAIN_URL
REACT_APP_REGION_MAPPING='{"NA": {"JSON_RTE_URL": "https://rte-extension.contentstack.com"},"EU": {"JSON_RTE_URL": "https://eu-rte-extension.contentstack.com"},"AZURE_NA": {"JSON_RTE_URL": "https://azure-na-rte-extension.contentstack.com"},"AZURE_EU": {"JSON_RTE_URL": "https://azure-eu-rte-extension.contentstack.com"},"GCP_NA": {"JSON_RTE_URL": "https://gcp-na-rte-extension.contentstack.com"}}'
# Start UI development server (from repo root: cd ui first)
cd ui
npm start
# UI runs on port 4000
# Start RTE webpack server (separate terminal, from ui/rte)
cd ui/rte
npm install
npm start
# RTE runs on port 1268
# Lint and format (from ui/)
npm run lint:fix
npm run prettifyWindows: Use npm run winStart in ui/ instead of npm run start if needed.
E2E tests run at the repository root. Configure Root .env (from .env.example) so login, org/API hosts, and app URLs match your stack and region. Start the UI so it is reachable at APP_HOST_URL (default http://localhost:4000 after cd ui && npm start).
For QA-style flows and asset tests, set ORGNAME, STACKNAME, BRANDFOLDER, STACK_NAME, and STACK_API_KEY as documented in the table above. Optional flags (INSTALL_VIA_MARKETPLACE, USE_GLOBAL_TEARDOWN, Brandfolder API keys, basic auth) are described in .env.example.
Run E2E:
# From repo root
npm run test:chrome
npm run test:firefox
npm run test:safari
npm run test:chrome-headed # headed browser
npm run show-report # open last HTML reportRun global setup first (login and auth token); it runs automatically before the test suite. To enable remote cleanup (uninstall app, delete content type) after the run, set USE_GLOBAL_TEARDOWN=true.
# Build and package (from repo root)
bash build.sh
# Output: to-deploy/ui.zip (contains ui/build/ and RTE build)To build without the script:
cd ui
npm run build
cd ui/rte
npm run buildUpload contents of ui/build/ (and RTE assets as required) to your static host (e.g. S3).
marketplace-brandfolder-app/
βββ ui/
β βββ src/
β β βββ common/
β β β βββ contexts/ # React contexts (MarketplaceApp, AppConfig, ConfigState, CustomField)
β β β βββ providers/ # Context providers
β β β βββ utils/ # Utility functions
β β β βββ constants/
β β βββ components/ # Reusable components (ErrorBoundary, Loaders, etc.)
β β βββ containers/
β β β βββ App/ # Main app router (HashRouter, routes)
β β β βββ ConfigScreen/ # App Configuration UI
β β β βββ CustomField/ # Custom Field (card/list)
β β β βββ SelectorPage/ # Selector Page UI
β β βββ root_config/ # Root config components for App Config & Custom Field
β β βββ __tests__/ # Jest unit tests
β β βββ index.tsx # Entry point
β βββ rte/ # JSON RTE plugin (Webpack, separate build)
β β βββ src/
β β βββ plugin.tsx # RTE plugin entry
β βββ index.html # HTML entry (Vite)
β βββ vite.config.ts # Vite configuration
β βββ jest.config.js # Jest configuration
β βββ package.json
βββ build.sh # Builds ui + ui/rte, outputs to-deploy/ui.zip
βββ TEMPLATE.md # Detailed app configuration and DAM setup
βββ package.json # Root scripts and husky
- MarketplaceAppProvider: Initializes Contentstack App SDK and shares it via
MarketplaceAppContext. - AppConfigProvider: Manages installation and config state from rootConfig via
AppConfigContext. - ConfigStateProvider: Local state for Config Screen UI via
ConfigStateContext. - CustomFieldProvider: State and data for Custom Field location via
CustomFieldContext.
- useAppLocation: Returns the location name and location instance from the app SDK (from
MarketplaceAppContext).
Routes map to UI locations; components are lazy-loaded.
| Path | UI Location | Source (container) |
|---|---|---|
/config |
Config Screen | containers/ConfigScreen |
/custom-field |
Custom Field | containers/CustomField |
/selector-page |
Selector Page | containers/SelectorPage |
Adding a route: Create the container, lazy-import it in ui/src/containers/App/index.tsx, and add a <Route> inside <Routes> wrapped in <Suspense> and any required providers.
The app uses SCSS for styling. Venus component styles are imported from @contentstack/venus-components/build/main.css.
| UI Location | Page Source |
|---|---|
| Config Screen | ui/src/containers/ConfigScreen/index.tsx |
| Custom Field | ui/src/containers/CustomField/index.tsx |
| Selector Page | ui/src/containers/SelectorPage/index.tsx |
| RTE | ui/rte/src/plugin.tsx |
- Open Developer Hub (or EU, Azure NA, Azure EU).
- Click + New App β choose Stack App β set name and description. See App Submission and Approval Guide to make the app public.
- In Basic Information, add the app icon.
- In UI Locations, set the app URL (e.g.
https://localhost:4000for local UI). - Add locations and paths (use HashRouter paths):
- App Configuration: path
/#/config - Custom Field: path
/#/custom-field, Data Type JSON - JSON RTE: path
/[your-app-name].js(localhost:https://localhost:1268/[your-app-name].js). For production, use your hosted RTE script path (e.g./dist/[your-app-name].js).
- App Configuration: path
- Enable Signed and Enabled where needed; add descriptions if desired.
- Click Install App and select the stack.
Note: Paths in
ui/src/containers/App/index.tsxmust match the paths set in Developer Hub.
For local development, JSON RTE uses a different port (1268). You can temporarily set App URL tohttps://localhost:1268and use path/[your-app-name].jsfor RTE; in that case Config and Custom Field locations will not be available from that URL.
- Create a feature branch from
main - Implement changes
- Add or update tests
- Update documentation
- Submit a pull request
- ESLint for linting (Airbnb + Prettier)
- Prettier for formatting
- Husky for git hooks; precommit runs prettify and lint:fix in
ui/
This project follows Conventional Commits:
feat: add Brandfolder asset filters
fix: resolve custom field save issue
docs: update README with env vars
test: add ConfigScreen tests
refactor: simplify CustomFieldProvider
- Run
bash build.sh(or buildui/andui/rte/as above). - Upload the built assets (e.g. from
to-deploy/ui.ziporui/build/) to your static host (e.g. AWS S3). - Set the app URL and paths in Developer Hub to your deployed URLs.
- Install the app in your stack.
npm install(root andui/,ui/rte/as needed).- Configure
.envinui/andui/rte/. - Run
npm startinui/for the main app; runnpm startinui/rte/for the RTE.
We welcome contributions.
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'feat: add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
npm install
cd ui && npm install
cd ui/rte && npm install
# Set .env in ui/ and ui/rte/
cd ../.. && cd ui && npm startThis project is licensed under the ISC License. See the LICENSE file for details.
- Contentstack for the marketplace platform
- Marketplace DAM App Boilerplate
- Vite for the build tool
- Issues: GitHub Issues
- Documentation: Contentstack Developer Hub
- Community: Contentstack Community
Made with β€οΈ by the Contentstack team