| Tool | Version |
|---|---|
| Node.js | ≥ 16.0.0 (CI runs on 22) |
| npm | ≥ 8.0.0 |
Use nvm or fnm to manage Node versions. The CI image is cimg/node:22.15.
# Clone the repo
git clone https://github.com/contentful/apps.git
cd apps
# Install root dependencies
npm ci
# Bootstrap the specific app(s) you want to work on
# (links cross-package deps, installs app node_modules)
SINCE=master npm run bootstrapWhy
SINCE=master? Lerna uses--sinceto only operate on packages changed relative to a base ref. SettingSINCE=masterbootstraps all packages that differ from master, plus their dependencies. On a fresh clone this typically covers everything you need.
To bootstrap a single app unconditionally:
cd apps/<app-name>
npm installcd apps/<app-name>
# Start the dev server (also creates/updates the App Definition in Contentful)
npm start
# or
npm run devThis runs Vite in watch mode and opens the app at http://localhost:3000. The app must be configured in a Contentful organization — use a personal dev space.
For apps with App Actions (mux, vercel, microsoft-teams, sap-commerce-cloud):
# Terminal 1 — frontend
cd apps/<app-name>/frontend && npm start
# Terminal 2 — app actions (if applicable)
cd apps/<app-name> && npm run start:functionsFor legacy lambda apps (netlify, typeform, slack, etc.):
cd apps/<app-name>/frontend && npm start
# Lambda runs in AWS — local dev typically uses the deployed staging lambda# All changed apps (from repo root)
npm test
# Single app
cd apps/<app-name>
npm run test # watch mode
npm run test:ci # single run (used in CI)Tests use Vitest + React Testing Library. Test files live alongside source (*.spec.tsx) or in a test/ directory.
# All changed apps (from repo root)
npm run build
# Single app
cd apps/<app-name>
npm run buildOutput goes to apps/<app-name>/build/ or apps/<app-name>/dist/.
# Lint changed apps (from root)
npm run lint
# Prettier check
npm run prettier:check
# Prettier fix
npm run prettier:write '**/*.{js,jsx,ts,tsx}'Prettier runs automatically on staged files via lint-staged (triggered by the husky pre-commit hook). Don't skip it.
- TypeScript + React + Vite + Vitest + Forma 36 in all apps.
useSDK()from@contentful/react-apps-toolkit— never accesswindow.contentfulExtensiondirectly.useAutoResizer()in Field, Sidebar, EntryEditor, and Dialog locations.sdk.app.setReady()after async initialization in ConfigScreen.- Forma 36 components only — no plain HTML divs for layout, no ad-hoc CSS. Use F36 tokens for spacing/color.
- No
anyin TypeScript — use explicit types orunknown. - Conventional Commits for all commit messages (
feat:,fix:,chore:,docs:, etc.). - Atomic commits — one logical change per commit.
When you make a non-obvious architectural decision — choosing between two viable approaches, accepting a known trade-off, or picking a pattern that future contributors might question — document it as an ADR.
When to write an ADR:
- Choosing an auth strategy (OAuth vs API key, etc.)
- Selecting a data storage location (installation parameters vs CMA entry vs external DB)
- Picking an integration pattern (App Functions vs Lambda vs webhook vs direct browser call)
- Adding a framework or library that isn't standard across the repo (React Router, Formik, etc.)
- Accepting a known limitation with a reason (e.g., "we use polling because webhooks require external infra")
When not to write an ADR:
- Standard Contentful app patterns already documented in
AGENTS.mdorARCHITECTURE.md - Routine dependency choices where there is only one real option
- Implementation details (naming, file structure, component decomposition)
Format and location:
apps/<app-name>/docs/ADRs/NNNN-short-title.md
Use the template below. The most important part is Context — explain what alternatives existed and why they were ruled out. A decision without that reasoning is just a fact; the ADR's value is the reasoning.
# ADR-NNNN: [Title]
**Date:** YYYY-MM-DD
**Status:** Proposed | Accepted | Deprecated | Superseded
**Deciders:** [names]
## Context
What problem are we solving? What alternatives did we consider and why were they ruled out?
## Decision
What are we doing?
## Consequences
### Positive
-
### Negative
-
### Neutral
-See apps/klaviyo/docs/ADRs/ for worked examples covering auth, API proxy, storage, event handling, navigation, and data conversion patterns.
- Use
create-contentful-appto scaffold:npx create-contentful-app@latest my-new-app mv my-new-app apps/
- Ensure
package.jsonhas aname,build,test:ci,lint, anddeployscript. - Add an
AGENTS.mdto the app root (see any existing app for the template). - Bootstrap from root:
SINCE=master npm run bootstrap - If the app involves non-obvious architectural choices, add ADRs under
apps/<app-name>/docs/ADRs/before opening a PR.
Deployments are managed by CircleCI. Manual deploys:
cd apps/<app-name>
# Deploy to production
npm run deploy
# Deploy to test/staging
npm run deploy:testRequires valid Contentful credentials and the correct organization context (CONTENTFUL_ORG_ID, CONTENTFUL_APP_DEF_ID).
Never deploy from a feature branch to production. Use the CI pipeline.
- Base branch:
master - Staging:
stagingbranch deploys to the staging environment - Feature branches:
<type>/<description>(e.g.feat/bulk-edit-locale-filter) - PRs require review from a code owner before merge
- CI must pass (lint, test, build) before merge
- Use Conventional Commits in PR titles — this drives semantic versioning for packages
Packages under packages/ (e.g. dam-app-base, ecommerce-app-base) are published to the GitHub npm registry. Releases are automated via:
npm run publish-packagesThis runs lerna version --conventional-commits (bumps versions based on commit history) then lerna publish from-git (publishes to https://npm.pkg.github.com/). Only run from CI with the correct GITHUB_PACKAGES_WRITE_TOKEN.
lerna bootstrap fails on a specific app
Run npm install directly in that app's directory to see the raw npm error.
App doesn't load in Contentful
Ensure npm start is running and the App Definition's frontend URL points to http://localhost:3000.
Type errors after pulling
Re-run bootstrap — a dependency may have been added: SINCE=master npm run bootstrap
Prettier pre-commit hook fails
Run npm run prettier:write '**/*.{js,jsx,ts,tsx}' and re-stage.
bedrock-content-generator build fails
This app uses LavaMoat. After bootstrap, run cd apps/bedrock-content-generator && npm run allow-scripts.